From f01ffbcc12c40c68a57bc2daffdfad697bc28921 Mon Sep 17 00:00:00 2001 From: Chen Yuheng <1016867898@qq.com> Date: Thu, 11 Jul 2024 10:33:43 +0800 Subject: [PATCH 001/127] Add oversampling and differential for g4 --- embassy-stm32/src/adc/g4.rs | 60 ++++++++++++++++++++ embassy-stm32/src/adc/mod.rs | 1 + examples/stm32g4/src/bin/adc_differential.rs | 47 +++++++++++++++ examples/stm32g4/src/bin/adc_oversampling.rs | 57 +++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 examples/stm32g4/src/bin/adc_differential.rs create mode 100644 examples/stm32g4/src/bin/adc_oversampling.rs diff --git a/embassy-stm32/src/adc/g4.rs b/embassy-stm32/src/adc/g4.rs index c1e584f59..896a70578 100644 --- a/embassy-stm32/src/adc/g4.rs +++ b/embassy-stm32/src/adc/g4.rs @@ -1,4 +1,8 @@ #[allow(unused)] + +#[cfg(stm32g4)] +use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs}; +#[cfg(stm32h7)] use pac::adc::vals::{Adcaldif, Difsel, Exten}; use pac::adccommon::vals::Presc; @@ -228,6 +232,62 @@ impl<'d, T: Instance> Adc<'d, T> { Vbat {} } + /// Enable differential channel. + /// Caution: + /// : When configuring the channel ā€œiā€ in differential input mode, its negative input voltage VINN[i] + /// is connected to another channel. As a consequence, this channel is no longer usable in + /// single-ended mode or in differential mode and must never be configured to be converted. + /// Some channels are shared between ADC1/ADC2/ADC3/ADC4/ADC5: this can make the + /// channel on the other ADC unusable. The only exception is when ADC master and the slave + /// operate in interleaved mode. + #[cfg(stm32g4)] + pub fn set_differential_channel(&mut self, ch: usize ,enable: bool) { + T::regs().cr().modify(|w| w.set_aden(false)); // disable adc + T::regs().difsel().modify(|w| { + w.set_difsel(ch, if enable { Difsel::DIFFERENTIAL } else { Difsel::SINGLEENDED }); + }); + T::regs().cr().modify(|w| w.set_aden(true)); + } + + #[cfg(stm32g4)] + pub fn set_differential(&mut self, channel: &mut impl AdcChannel, enable: bool) { + self.set_differential_channel(channel.channel() as usize, enable); + } + + /// Set oversampling shift. + #[cfg(stm32g4)] + pub fn set_oversampling_shift(&mut self, shift: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + + /// Set oversampling ratio. + #[cfg(stm32g4)] + pub fn set_oversampling_ratio(&mut self, ratio: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + /// Enable oversampling in regular mode. + #[cfg(stm32g4)] + pub fn enable_regular_oversampling_mode(&mut self,mode:Rovsm,trig_mode:Trovs, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + } + + // Reads that are not implemented as INJECTED in "blocking_read" + // #[cfg(stm32g4)] + // pub fn enalble_injected_oversampling_mode(&mut self, enable: bool) { + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + + // #[cfg(stm32g4)] + // pub fn enable_oversampling_regular_injected_mode(&mut self, enable: bool) { + // // the regularoversampling mode is forced to resumed mode (ROVSM bit ignored), + // T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + + /// Set the ADC sample time. pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 7a7d7cd8e..b530bb4c8 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -26,6 +26,7 @@ use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(any(adc_f1, adc_f3_v2)))] pub use crate::pac::adc::vals::Res as Resolution; pub use crate::pac::adc::vals::SampleTime; +pub use crate::pac::adc::vals ; use crate::peripherals; dma_trait!(RxDma, Instance); diff --git a/examples/stm32g4/src/bin/adc_differential.rs b/examples/stm32g4/src/bin/adc_differential.rs new file mode 100644 index 000000000..78d071d45 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_differential.rs @@ -0,0 +1,47 @@ +//! adc differential mode example +//! +//! This example uses adc1 in differential mode +//! p:pa0 n:pa1 + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES247_5); + adc.set_differential(&mut p.PA0, true); //p:pa0,n:pa1 + + // can also use + // adc.set_differential_channel(1, true); + info!("adc initialized"); + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32g4/src/bin/adc_oversampling.rs b/examples/stm32g4/src/bin/adc_oversampling.rs new file mode 100644 index 000000000..d31eb20f8 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_oversampling.rs @@ -0,0 +1,57 @@ +//! adc oversampling example +//! +//! This example uses adc oversampling to achieve 16bit data + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::vals::{Rovsm, Trovs}; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES6_5); + // From https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // page652 Oversampler + // Table 172. Maximum output results vs N and M. Grayed values indicates truncation + // 0x00 oversampling ratio X2 + // 0x01 oversampling ratio X4 + // 0x02 oversampling ratio X8 + // 0x03 oversampling ratio X16 + // 0x04 oversampling ratio X32 + // 0x05 oversampling ratio X64 + // 0x06 oversampling ratio X128 + // 0x07 oversampling ratio X256 + adc.set_oversampling_ratio(0x03); // ratio X3 + adc.set_oversampling_shift(0b0000); // no shift + adc.enable_regular_oversampling_mode(Rovsm::RESUMED, Trovs::AUTOMATIC, true); + + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: 0x{:X}", measured); //max 0xFFF0 -> 65520 + Timer::after_millis(500).await; + } +} From fd5501d455f2e2e6aab9f09767599fcb16d33937 Mon Sep 17 00:00:00 2001 From: Chen Yuheng <1016867898@qq.com> Date: Thu, 11 Jul 2024 15:34:34 +0800 Subject: [PATCH 002/127] delete unused "info!" and fmt --- embassy-stm32/src/adc/g4.rs | 22 ++++++++++++++-------- embassy-stm32/src/adc/mod.rs | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/embassy-stm32/src/adc/g4.rs b/embassy-stm32/src/adc/g4.rs index 896a70578..3e9ba8ae2 100644 --- a/embassy-stm32/src/adc/g4.rs +++ b/embassy-stm32/src/adc/g4.rs @@ -1,9 +1,9 @@ #[allow(unused)] - -#[cfg(stm32g4)] -use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs}; #[cfg(stm32h7)] use pac::adc::vals::{Adcaldif, Difsel, Exten}; +#[allow(unused)] +#[cfg(stm32g4)] +use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs}; use pac::adccommon::vals::Presc; use super::{blocking_delay_us, Adc, AdcChannel, Instance, Resolution, SampleTime}; @@ -241,10 +241,17 @@ impl<'d, T: Instance> Adc<'d, T> { /// channel on the other ADC unusable. The only exception is when ADC master and the slave /// operate in interleaved mode. #[cfg(stm32g4)] - pub fn set_differential_channel(&mut self, ch: usize ,enable: bool) { + pub fn set_differential_channel(&mut self, ch: usize, enable: bool) { T::regs().cr().modify(|w| w.set_aden(false)); // disable adc T::regs().difsel().modify(|w| { - w.set_difsel(ch, if enable { Difsel::DIFFERENTIAL } else { Difsel::SINGLEENDED }); + w.set_difsel( + ch, + if enable { + Difsel::DIFFERENTIAL + } else { + Difsel::SINGLEENDED + }, + ); }); T::regs().cr().modify(|w| w.set_aden(true)); } @@ -268,8 +275,8 @@ impl<'d, T: Instance> Adc<'d, T> { /// Enable oversampling in regular mode. #[cfg(stm32g4)] - pub fn enable_regular_oversampling_mode(&mut self,mode:Rovsm,trig_mode:Trovs, enable: bool) { - T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); + pub fn enable_regular_oversampling_mode(&mut self, mode: Rovsm, trig_mode: Trovs, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode)); T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); } @@ -287,7 +294,6 @@ impl<'d, T: Instance> Adc<'d, T> { // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); // } - /// Set the ADC sample time. pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index b530bb4c8..30f7de09b 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -23,10 +23,10 @@ pub use _version::*; #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] use embassy_sync::waitqueue::AtomicWaker; +pub use crate::pac::adc::vals; #[cfg(not(any(adc_f1, adc_f3_v2)))] pub use crate::pac::adc::vals::Res as Resolution; pub use crate::pac::adc::vals::SampleTime; -pub use crate::pac::adc::vals ; use crate::peripherals; dma_trait!(RxDma, Instance); From a7258927209827e27f4190af57020c2a8017dbaa Mon Sep 17 00:00:00 2001 From: Karun Date: Mon, 5 Aug 2024 15:37:03 -0400 Subject: [PATCH 003/127] Convert uart half_duplex to use open-drain pull-up --- embassy-stm32/src/usart/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 7ed3793a1..3283fb6b7 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -1029,8 +1029,9 @@ impl<'d> Uart<'d, Async> { /// (when it is available for your chip). There is no functional difference between these methods, as both /// allow bidirectional communication. /// - /// The pin is always released when no data is transmitted. Thus, it acts as a standard - /// I/O in idle or in reception. + /// The TX pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. It means that the I/O must be configured so that TX is + /// configured as alternate function open-drain with an external pull-up /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict /// on the line must be managed by software (for instance by using a centralized arbiter). #[doc(alias("HDSEL"))] @@ -1051,7 +1052,7 @@ impl<'d> Uart<'d, Async> { Self::new_inner( peri, None, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(tx, AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up)), None, None, None, From 446169b2c1c85ced844e1a888d7fec75047e11c1 Mon Sep 17 00:00:00 2001 From: Karun Date: Tue, 6 Aug 2024 11:52:16 -0400 Subject: [PATCH 004/127] Add gpio version dependency Add configurable output type for half-duplex --- embassy-stm32/src/usart/mod.rs | 81 ++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 3283fb6b7..0c5bbf491 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -211,6 +211,19 @@ impl Default for Config { } } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Half duplex IO mode +pub enum HalfDuplexConfig { + /// Push pull allows for faster baudrates, may require series resistor + PushPull, + /// Open drain output using external pull up resistor + OpenDrainExternal, + #[cfg(not(gpio_v1))] + /// Open drain output using internal pull up resistor + OpenDrainInternal, +} + /// Serial error #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -1042,6 +1055,7 @@ impl<'d> Uart<'d, Async> { tx_dma: impl Peripheral

> + 'd, rx_dma: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { #[cfg(not(any(usart_v1, usart_v2)))] { @@ -1052,7 +1066,21 @@ impl<'d> Uart<'d, Async> { Self::new_inner( peri, None, - new_pin!(tx, AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up)), + new_pin!( + tx, + match half_duplex { + HalfDuplexConfig::PushPull => { + AfType::output(OutputType::PushPull, Speed::Medium) + } + HalfDuplexConfig::OpenDrainExternal => { + AfType::output(OutputType::OpenDrain, Speed::Medium) + } + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => { + AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) + } + } + ), None, None, None, @@ -1080,6 +1108,7 @@ impl<'d> Uart<'d, Async> { tx_dma: impl Peripheral

> + 'd, rx_dma: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { config.swap_rx_tx = true; config.half_duplex = true; @@ -1088,7 +1117,21 @@ impl<'d> Uart<'d, Async> { peri, None, None, - new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!( + rx, + match half_duplex { + HalfDuplexConfig::PushPull => { + AfType::output(OutputType::PushPull, Speed::Medium) + } + HalfDuplexConfig::OpenDrainExternal => { + AfType::output(OutputType::OpenDrain, Speed::Medium) + } + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => { + AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) + } + } + ), None, None, new_dma!(tx_dma), @@ -1193,6 +1236,7 @@ impl<'d> Uart<'d, Blocking> { peri: impl Peripheral

+ 'd, tx: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { #[cfg(not(any(usart_v1, usart_v2)))] { @@ -1203,7 +1247,21 @@ impl<'d> Uart<'d, Blocking> { Self::new_inner( peri, None, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!( + tx, + match half_duplex { + HalfDuplexConfig::PushPull => { + AfType::output(OutputType::PushPull, Speed::Medium) + } + HalfDuplexConfig::OpenDrainExternal => { + AfType::output(OutputType::OpenDrain, Speed::Medium) + } + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => { + AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) + } + } + ), None, None, None, @@ -1228,6 +1286,7 @@ impl<'d> Uart<'d, Blocking> { peri: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { config.swap_rx_tx = true; config.half_duplex = true; @@ -1236,7 +1295,21 @@ impl<'d> Uart<'d, Blocking> { peri, None, None, - new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!( + rx, + match half_duplex { + HalfDuplexConfig::PushPull => { + AfType::output(OutputType::PushPull, Speed::Medium) + } + HalfDuplexConfig::OpenDrainExternal => { + AfType::output(OutputType::OpenDrain, Speed::Medium) + } + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => { + AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) + } + } + ), None, None, None, From 1b0661ebb17fca93d11891a1c488005d3d644784 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Sun, 18 Aug 2024 21:06:13 +0200 Subject: [PATCH 005/127] [UCPD] Add support for non-SOP packets Allow capturing (and distinguishing) non-SOP packets as well. The default configuration will just configure SOP packets. For ease of use the default receive function signature is unchanged as for PD sinks (which is likely the common usage) just SOP is enough so no need to differentiate. --- embassy-stm32/src/ucpd.rs | 77 ++++++++++++++++++++++++++-- examples/stm32g4/src/bin/usb_c_pd.rs | 2 +- tests/stm32/src/bin/ucpd.rs | 4 +- 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 40aea75cb..ee0a2c7c1 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -27,7 +27,7 @@ use crate::dma::{ChannelAndRequest, TransferOptions}; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; -pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; +pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, Rxordset, TypecVstateCc as CcVState}; use crate::rcc::{self, RccPeripheral}; pub(crate) fn init( @@ -86,6 +86,34 @@ pub enum CcPull { Source3_0A, } +/// UCPD configuration +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Receive SOP packets + pub sop: bool, + /// Receive SOP' packets + pub sop_prime: bool, + /// Receive SOP'' packets + pub sop_double_prime: bool, + /// Receive SOP'_Debug packets + pub sop_prime_debug: bool, + /// Receive SOP''_Debug packets + pub sop_double_prime_debug: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + sop: true, + sop_prime: false, + sop_double_prime: false, + sop_prime_debug: false, + sop_double_prime_debug: false, + } + } +} + /// UCPD driver. pub struct Ucpd<'d, T: Instance> { cc_phy: CcPhy<'d, T>, @@ -98,6 +126,7 @@ impl<'d, T: Instance> Ucpd<'d, T> { _irq: impl interrupt::typelevel::Binding> + 'd, cc1: impl Peripheral

> + 'd, cc2: impl Peripheral

> + 'd, + config: Config, ) -> Self { into_ref!(cc1, cc2); cc1.set_as_analog(); @@ -129,9 +158,15 @@ impl<'d, T: Instance> Ucpd<'d, T> { // 1.75us * 17 = ~30us w.set_ifrgap(17 - 1); - // TODO: Currently only hard reset and SOP messages can be received. // UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing). - w.set_rxordseten(0b1001); + let rxordset = (config.sop as u16) << 0 + | (config.sop_prime as u16) << 1 + | (config.sop_double_prime as u16) << 2 + // Hard reset + | 0x1 << 3 + | (config.sop_prime_debug as u16) << 4 + | (config.sop_double_prime_debug as u16) << 5; + w.set_rxordseten(rxordset); // Enable DMA w.set_txdmaen(true); @@ -288,6 +323,22 @@ impl<'d, T: Instance> CcPhy<'d, T> { } } +/// Receive SOP. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Sop { + /// SOP + Sop, + /// SOP' + SopPrime, + /// SOP'' + SopDoublePrime, + /// SOP'_Debug + SopPrimeDebug, + /// SOP''_Debug + SopDoublePrimeDebug, +} + /// Receive Error. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -340,6 +391,13 @@ impl<'d, T: Instance> PdPhy<'d, T> { /// /// Returns the number of received bytes or an error. pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + self.receive_with_sop(buf).await.map(|(_sop, size)| size) + } + + /// Receives SOP and a PD message into the provided buffer. + /// + /// Returns the start of packet type and number of received bytes or an error. + pub async fn receive_with_sop(&mut self, buf: &mut [u8]) -> Result<(Sop, usize), RxError> { let r = T::REGS; let dma = unsafe { @@ -388,7 +446,18 @@ impl<'d, T: Instance> PdPhy<'d, T> { } } - Ok(r.rx_payszr().read().rxpaysz().into()) + let sop = match r.rx_ordsetr().read().rxordset() { + Rxordset::SOP => Sop::Sop, + Rxordset::SOPPRIME => Sop::SopPrime, + Rxordset::SOPDOUBLEPRIME => Sop::SopDoublePrime, + Rxordset::SOPPRIMEDEBUG => Sop::SopPrimeDebug, + Rxordset::SOPDOUBLEPRIMEDEBUG => Sop::SopDoublePrimeDebug, + Rxordset::CABLERESET => return Err(RxError::HardReset), + // Extension headers are not supported + _ => unreachable!(), + }; + + Ok((sop, r.rx_payszr().read().rxpaysz().into())) } fn enable_rx_interrupt(enable: bool) { diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 7caea634f..2e87d3931 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4); + let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4, Default::default()); ucpd.cc_phy().set_pull(CcPull::Sink); info!("Waiting for USB connection..."); diff --git a/tests/stm32/src/bin/ucpd.rs b/tests/stm32/src/bin/ucpd.rs index a6d13b34a..bd7b35d6b 100644 --- a/tests/stm32/src/bin/ucpd.rs +++ b/tests/stm32/src/bin/ucpd.rs @@ -106,8 +106,8 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); // Wire between PD0 and PA8 - let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15); - let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2); + let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15, Default::default()); + let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2, Default::default()); join( source(ucpd1, p.DMA1_CH1, p.DMA1_CH2), From fcf9b3239e9c845d8f2b4eb5aea853f7ce377bf1 Mon Sep 17 00:00:00 2001 From: Karun Date: Mon, 19 Aug 2024 11:27:18 -0400 Subject: [PATCH 006/127] remove duplication --- embassy-stm32/src/usart/mod.rs | 77 +++++++--------------------------- 1 file changed, 16 insertions(+), 61 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 0c5bbf491..4f2ff8b2a 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -14,7 +14,7 @@ use embassy_sync::waitqueue::AtomicWaker; use futures_util::future::{select, Either}; use crate::dma::ChannelAndRequest; -use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::gpio::{self, AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::interrupt::typelevel::Interrupt as _; use crate::interrupt::{self, Interrupt, InterruptExt}; use crate::mode::{Async, Blocking, Mode}; @@ -224,6 +224,17 @@ pub enum HalfDuplexConfig { OpenDrainInternal, } +impl HalfDuplexConfig { + pub fn af_type(self) -> gpio::AfType { + match self { + HalfDuplexConfig::PushPull => AfType::output(OutputType::PushPull, Speed::Medium), + HalfDuplexConfig::OpenDrainExternal => AfType::output(OutputType::OpenDrain, Speed::Medium), + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up), + } + } +} + /// Serial error #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -1066,21 +1077,7 @@ impl<'d> Uart<'d, Async> { Self::new_inner( peri, None, - new_pin!( - tx, - match half_duplex { - HalfDuplexConfig::PushPull => { - AfType::output(OutputType::PushPull, Speed::Medium) - } - HalfDuplexConfig::OpenDrainExternal => { - AfType::output(OutputType::OpenDrain, Speed::Medium) - } - #[cfg(not(gpio_v1))] - HalfDuplexConfig::OpenDrainInternal => { - AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) - } - } - ), + new_pin!(tx, half_duplex.af_type()), None, None, None, @@ -1117,21 +1114,7 @@ impl<'d> Uart<'d, Async> { peri, None, None, - new_pin!( - rx, - match half_duplex { - HalfDuplexConfig::PushPull => { - AfType::output(OutputType::PushPull, Speed::Medium) - } - HalfDuplexConfig::OpenDrainExternal => { - AfType::output(OutputType::OpenDrain, Speed::Medium) - } - #[cfg(not(gpio_v1))] - HalfDuplexConfig::OpenDrainInternal => { - AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) - } - } - ), + new_pin!(rx, half_duplex.af_type()), None, None, new_dma!(tx_dma), @@ -1247,21 +1230,7 @@ impl<'d> Uart<'d, Blocking> { Self::new_inner( peri, None, - new_pin!( - tx, - match half_duplex { - HalfDuplexConfig::PushPull => { - AfType::output(OutputType::PushPull, Speed::Medium) - } - HalfDuplexConfig::OpenDrainExternal => { - AfType::output(OutputType::OpenDrain, Speed::Medium) - } - #[cfg(not(gpio_v1))] - HalfDuplexConfig::OpenDrainInternal => { - AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) - } - } - ), + new_pin!(tx, half_duplex.af_type()), None, None, None, @@ -1295,21 +1264,7 @@ impl<'d> Uart<'d, Blocking> { peri, None, None, - new_pin!( - rx, - match half_duplex { - HalfDuplexConfig::PushPull => { - AfType::output(OutputType::PushPull, Speed::Medium) - } - HalfDuplexConfig::OpenDrainExternal => { - AfType::output(OutputType::OpenDrain, Speed::Medium) - } - #[cfg(not(gpio_v1))] - HalfDuplexConfig::OpenDrainInternal => { - AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up) - } - } - ), + new_pin!(rx, half_duplex.af_type()), None, None, None, From bbc06035c136887aad91a1944efcc4c98401866f Mon Sep 17 00:00:00 2001 From: Karun Date: Mon, 19 Aug 2024 12:15:39 -0400 Subject: [PATCH 007/127] make half duplex fn private --- embassy-stm32/src/usart/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 4f2ff8b2a..89d92dda2 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -225,7 +225,7 @@ pub enum HalfDuplexConfig { } impl HalfDuplexConfig { - pub fn af_type(self) -> gpio::AfType { + fn af_type(self) -> gpio::AfType { match self { HalfDuplexConfig::PushPull => AfType::output(OutputType::PushPull, Speed::Medium), HalfDuplexConfig::OpenDrainExternal => AfType::output(OutputType::OpenDrain, Speed::Medium), From aff66b9695a70222b20c19585f04df2ecbabccb1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 21 Jun 2024 20:17:39 +0200 Subject: [PATCH 008/127] nrf: add try_write to BufferedUarte. --- embassy-nrf/src/buffered_uarte.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 159b4db8f..6d39597c6 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -358,6 +358,11 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { self.tx.write(buf).await } + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + self.tx.try_write(buf) + } + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. pub async fn flush(&mut self) -> Result<(), Error> { self.tx.flush().await @@ -482,6 +487,29 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { .await } + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + //trace!("poll_write: {:?}", buf.len()); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + return Ok(0); + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + U::Interrupt::pend(); + + Ok(n) + } + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. pub async fn flush(&mut self) -> Result<(), Error> { poll_fn(move |cx| { From 160e1c38ceab0ae8876c2bf5f12438edd4d9b018 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 21 Jun 2024 20:19:54 +0200 Subject: [PATCH 009/127] Add embassy-net-nrf91. --- embassy-net-nrf91/Cargo.toml | 37 + embassy-net-nrf91/README.md | 9 + embassy-net-nrf91/src/fmt.rs | 274 ++++++ embassy-net-nrf91/src/lib.rs | 970 +++++++++++++++++++ examples/nrf9160/.cargo/config.toml | 3 +- examples/nrf9160/Cargo.toml | 5 + examples/nrf9160/memory.x | 8 +- examples/nrf9160/src/bin/modem_tcp_client.rs | 250 +++++ 8 files changed, 1553 insertions(+), 3 deletions(-) create mode 100644 embassy-net-nrf91/Cargo.toml create mode 100644 embassy-net-nrf91/README.md create mode 100644 embassy-net-nrf91/src/fmt.rs create mode 100644 embassy-net-nrf91/src/lib.rs create mode 100644 examples/nrf9160/src/bin/modem_tcp_client.rs diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml new file mode 100644 index 000000000..2156346a4 --- /dev/null +++ b/embassy-net-nrf91/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "embassy-net-nrf91" +version = "0.1.0" +edition = "2021" +description = "embassy-net driver for Nordic nRF91-series cellular modems" +keywords = ["embedded", "nrf91", "embassy-net", "cellular"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-nrf91" + +[features] +defmt = [ "dep:defmt", "heapless/defmt-03" ] +log = [ "dep:log" ] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +nrf9160-pac = { version = "0.12.0" } + +embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-sync = { version = "0.6.0", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"} + +heapless = "0.8" +embedded-io = "0.6.1" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy-net-nrf91/README.md b/embassy-net-nrf91/README.md new file mode 100644 index 000000000..30da71787 --- /dev/null +++ b/embassy-net-nrf91/README.md @@ -0,0 +1,9 @@ +# nRF91 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems. + +See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-net-nrf91/src/fmt.rs b/embassy-net-nrf91/src/fmt.rs new file mode 100644 index 000000000..35b929fde --- /dev/null +++ b/embassy-net-nrf91/src/fmt.rs @@ -0,0 +1,274 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs new file mode 100644 index 000000000..62ade6dd3 --- /dev/null +++ b/embassy-net-nrf91/src/lib.rs @@ -0,0 +1,970 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// must be first +mod fmt; + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::{self, MaybeUninit}; +use core::ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping}; +use core::slice; +use core::sync::atomic::{compiler_fence, fence, Ordering}; +use core::task::{Poll, Waker}; + +use embassy_net_driver_channel as ch; +use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; +use heapless::Vec; +use nrf9160_pac as pac; +use pac::NVIC; + +const RX_SIZE: usize = 8 * 1024; +const TRACE_SIZE: usize = 16 * 1024; +const MTU: usize = 1500; + +/// Network driver. +/// +/// This is the type you have to pass to `embassy-net` when creating the network stack. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// Call this function on IPC IRQ +pub fn on_ipc_irq() { + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + + trace!("irq"); + + ipc.inten.write(|w| w); + WAKER.wake(); +} + +struct Allocator<'a> { + start: *mut u8, + end: *mut u8, + _phantom: PhantomData<&'a mut u8>, +} + +impl<'a> Allocator<'a> { + fn alloc_bytes(&mut self, size: usize) -> &'a mut [MaybeUninit] { + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = self.start; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is in-bounds. + unsafe { slice::from_raw_parts_mut(p as *mut _, size) } + } + + fn alloc(&mut self) -> &'a mut MaybeUninit { + let align = mem::align_of::(); + let size = mem::size_of::(); + + let align_size = match (self.start as usize) % align { + 0 => 0, + n => align - n, + }; + + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if align_size + size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = unsafe { self.start.add(align_size) }; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is aligned and in-bounds. + unsafe { &mut *(p as *mut _) } + } +} + +/// Create a new nRF91 embassy-net driver. +pub async fn new<'a, TW: embedded_io::Write>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_writer: TW, +) -> (NetDriver<'a>, Control<'a>, Runner<'a, TW>) { + let shmem_len = shmem.len(); + let shmem_ptr = shmem.as_mut_ptr() as *mut u8; + + const SPU_REGION_SIZE: usize = 8192; // 8kb + assert!(shmem_len != 0); + assert!( + shmem_len % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize) % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize + shmem_len) < 0x2002_0000, + "shmem must be in the lower 128kb of RAM" + ); + + let spu = unsafe { &*pac::SPU_S::ptr() }; + debug!("Setting IPC RAM as nonsecure..."); + let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE; + let region_end = region_start + shmem_len / SPU_REGION_SIZE; + for i in region_start..region_end { + spu.ramregion[i].perm.write(|w| { + w.execute().set_bit(); + w.write().set_bit(); + w.read().set_bit(); + w.secattr().clear_bit(); + w.lock().clear_bit(); + w + }) + } + + spu.periphid[42].perm.write(|w| w.secattr().non_secure()); + + let mut alloc = Allocator { + start: shmem_ptr, + end: unsafe { shmem_ptr.add(shmem_len) }, + _phantom: PhantomData, + }; + + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + let power = unsafe { &*pac::POWER_S::ptr() }; + + let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() }); + let rx = alloc.alloc_bytes(RX_SIZE); + let trace = alloc.alloc_bytes(TRACE_SIZE); + + cb.version = 0x00010000; + cb.rx_base = rx.as_mut_ptr() as _; + cb.rx_size = RX_SIZE; + cb.control_list_ptr = &mut cb.lists[0]; + cb.data_list_ptr = &mut cb.lists[1]; + cb.modem_info_ptr = &mut cb.modem_info; + cb.trace_ptr = &mut cb.trace; + cb.lists[0].len = LIST_LEN; + cb.lists[1].len = LIST_LEN; + cb.trace.base = trace.as_mut_ptr() as _; + cb.trace.size = TRACE_SIZE; + + ipc.gpmem[0].write(|w| unsafe { w.bits(cb as *mut _ as u32) }); + ipc.gpmem[1].write(|w| unsafe { w.bits(0) }); + + // connect task/event i to channel i + for i in 0..8 { + ipc.send_cnf[i].write(|w| unsafe { w.bits(1 << i) }); + ipc.receive_cnf[i].write(|w| unsafe { w.bits(1 << i) }); + } + + compiler_fence(Ordering::SeqCst); + + // POWER.LTEMODEM.STARTN = 0 + // The reg is missing in the PAC?? + let startn = unsafe { (power as *const _ as *mut u32).add(0x610 / 4) }; + unsafe { startn.write_volatile(0) } + + unsafe { NVIC::unmask(pac::Interrupt::IPC) }; + + let state_inner = &*state.inner.write(RefCell::new(StateInner { + init: false, + init_waker: WakerRegistration::new(), + cb, + requests: [const { None }; REQ_COUNT], + next_req_serial: 0x12345678, + + rx_control_list: ptr::null_mut(), + rx_data_list: ptr::null_mut(), + rx_seq_no: 0, + rx_check: PointerChecker { + start: rx.as_mut_ptr() as *mut u8, + end: (rx.as_mut_ptr() as *mut u8).wrapping_add(RX_SIZE), + }, + + tx_seq_no: 0, + tx_buf_used: [false; TX_BUF_COUNT], + + trace_chans: Vec::new(), + trace_check: PointerChecker { + start: trace.as_mut_ptr() as *mut u8, + end: (trace.as_mut_ptr() as *mut u8).wrapping_add(TRACE_SIZE), + }, + })); + + let control = Control { state: state_inner }; + + let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); + let state_ch = ch_runner.state_runner(); + state_ch.set_link_state(ch::driver::LinkState::Up); + + let runner = Runner { + ch: ch_runner, + state: state_inner, + trace_writer, + }; + + (device, control, runner) +} + +/// Shared state for the drivver. +pub struct State { + ch: ch::State, + inner: MaybeUninit>, +} + +impl State { + /// Create a new State. + pub const fn new() -> Self { + Self { + ch: ch::State::new(), + inner: MaybeUninit::uninit(), + } + } +} + +const TX_BUF_COUNT: usize = 4; +const TX_BUF_SIZE: usize = 1024; + +struct TraceChannelInfo { + ptr: *mut TraceChannel, + start: *mut u8, + end: *mut u8, +} + +const REQ_COUNT: usize = 4; + +struct PendingRequest { + req_serial: u32, + resp_msg: *mut Message, + waker: Waker, +} + +struct StateInner { + init: bool, + init_waker: WakerRegistration, + + cb: *mut ControlBlock, + requests: [Option; REQ_COUNT], + next_req_serial: u32, + + rx_control_list: *mut List, + rx_data_list: *mut List, + rx_seq_no: u16, + rx_check: PointerChecker, + + tx_seq_no: u16, + tx_buf_used: [bool; TX_BUF_COUNT], + + trace_chans: Vec, + trace_check: PointerChecker, +} + +impl StateInner { + fn poll(&mut self, trace_writer: &mut impl embedded_io::Write, ch: &mut ch::Runner) { + trace!("poll!"); + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + + if ipc.events_receive[0].read().bits() != 0 { + ipc.events_receive[0].reset(); + trace!("ipc 0"); + } + + if ipc.events_receive[2].read().bits() != 0 { + ipc.events_receive[2].reset(); + trace!("ipc 2"); + + if !self.init { + let desc = unsafe { addr_of!((*self.cb).modem_info).read_volatile() }; + assert_eq!(desc.version, 1); + + self.rx_check.check_mut(desc.control_list_ptr); + self.rx_check.check_mut(desc.data_list_ptr); + + self.rx_control_list = desc.control_list_ptr; + self.rx_data_list = desc.data_list_ptr; + let rx_control_len = unsafe { addr_of!((*self.rx_control_list).len).read_volatile() }; + let rx_data_len = unsafe { addr_of!((*self.rx_data_list).len).read_volatile() }; + assert_eq!(rx_control_len, LIST_LEN); + assert_eq!(rx_data_len, LIST_LEN); + self.init = true; + + debug!("IPC initialized OK!"); + self.init_waker.wake(); + } + } + + if ipc.events_receive[4].read().bits() != 0 { + ipc.events_receive[4].reset(); + trace!("ipc 4"); + + loop { + let list = unsafe { &mut *self.rx_control_list }; + let control_work = self.process(list, true, ch); + let list = unsafe { &mut *self.rx_data_list }; + let data_work = self.process(list, false, ch); + if !control_work && !data_work { + break; + } + } + } + + if ipc.events_receive[6].read().bits() != 0 { + ipc.events_receive[6].reset(); + trace!("ipc 6"); + } + + if ipc.events_receive[7].read().bits() != 0 { + ipc.events_receive[7].reset(); + trace!("ipc 7: trace"); + + let msg = unsafe { addr_of!((*self.cb).trace.rx_state).read_volatile() }; + if msg != 0 { + trace!("trace msg {}", msg); + match msg { + 0 => unreachable!(), + 1 => { + let ctx = unsafe { addr_of!((*self.cb).trace.rx_ptr).read_volatile() } as *mut TraceContext; + debug!("trace init: {:?}", ctx); + self.trace_check.check(ctx); + let chans = unsafe { addr_of!((*ctx).chans).read_volatile() }; + for chan_ptr in chans { + let chan = self.trace_check.check_read(chan_ptr); + self.trace_check.check(chan.start); + self.trace_check.check(chan.end); + assert!(chan.start < chan.end); + self.trace_chans + .push(TraceChannelInfo { + ptr: chan_ptr, + start: chan.start, + end: chan.end, + }) + .map_err(|_| ()) + .unwrap() + } + } + 2 => { + for chan_info in &self.trace_chans { + let read_ptr = unsafe { addr_of!((*chan_info.ptr).read_ptr).read_volatile() }; + let write_ptr = unsafe { addr_of!((*chan_info.ptr).write_ptr).read_volatile() }; + assert!(read_ptr >= chan_info.start && read_ptr <= chan_info.end); + assert!(write_ptr >= chan_info.start && write_ptr <= chan_info.end); + if read_ptr != write_ptr { + let id = unsafe { addr_of!((*chan_info.ptr).id).read_volatile() }; + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + if read_ptr < write_ptr { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, write_ptr.offset_from(read_ptr) as _) + }); + } else { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, chan_info.end.offset_from(read_ptr) as _) + }); + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts( + chan_info.start, + write_ptr.offset_from(chan_info.start) as _, + ) + }); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + unsafe { addr_of_mut!((*chan_info.ptr).read_ptr).write_volatile(write_ptr) }; + } + } + } + _ => warn!("unknown trace msg {}", msg), + } + unsafe { addr_of_mut!((*self.cb).trace.rx_state).write_volatile(0) }; + } + } + + ipc.intenset.write(|w| { + w.receive0().set_bit(); + w.receive2().set_bit(); + w.receive4().set_bit(); + w.receive6().set_bit(); + w.receive7().set_bit(); + w + }); + } + + fn handle_trace(writer: &mut impl embedded_io::Write, id: u8, data: &[u8]) { + trace!("trace: {} {}", id, data.len()); + let mut header = [0u8; 5]; + header[0] = 0xEF; + header[1] = 0xBE; + header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); + header[4] = id; + writer.write_all(&header).unwrap(); + writer.write_all(data).unwrap(); + } + + fn process(&mut self, list: *mut List, is_control: bool, ch: &mut ch::Runner) -> bool { + let mut did_work = false; + for i in 0..LIST_LEN { + let item_ptr = unsafe { addr_of_mut!((*list).items[i]) }; + let preamble = unsafe { addr_of!((*item_ptr).state).read_volatile() }; + if preamble & 0xFF == 0x01 && preamble >> 16 == self.rx_seq_no as u32 { + let msg_ptr = unsafe { addr_of!((*item_ptr).message).read_volatile() }; + let msg = self.rx_check.check_read(msg_ptr); + + debug!("rx seq {} msg: {:?}", preamble >> 16, msg); + + if is_control { + self.handle_control(&msg); + } else { + self.handle_data(&msg, ch); + } + + unsafe { addr_of_mut!((*item_ptr).state).write_volatile(0x03) }; + self.rx_seq_no = self.rx_seq_no.wrapping_add(1); + + did_work = true; + } + } + did_work + } + + fn find_free_message(&mut self, ch: usize) -> Option { + for i in 0..LIST_LEN { + let preamble = unsafe { addr_of!((*self.cb).lists[ch].items[i].state).read_volatile() }; + if matches!(preamble & 0xFF, 0 | 3) { + trace!("using tx msg idx {}", i); + return Some(i); + } + } + return None; + } + + fn find_free_tx_buf(&mut self) -> Option { + for i in 0..TX_BUF_COUNT { + if !self.tx_buf_used[i] { + trace!("using tx buf idx {}", i); + return Some(i); + } + } + return None; + } + + fn send_message(&mut self, msg: &mut Message, data: &[u8]) { + if data.is_empty() { + msg.data = ptr::null_mut(); + msg.data_len = 0; + } else { + assert!(data.len() <= TX_BUF_SIZE); + let buf_idx = self.find_free_tx_buf().unwrap(); // TODO handle out of bufs + let buf = unsafe { addr_of_mut!((*self.cb).tx_bufs[buf_idx]) } as *mut u8; + unsafe { copy_nonoverlapping(data.as_ptr(), buf, data.len()) } + msg.data = buf; + msg.data_len = data.len(); + self.tx_buf_used[buf_idx] = true; + + fence(Ordering::SeqCst); // synchronize copy_nonoverlapping (non-volatile) with volatile writes below. + } + + // TODO free data buf if send_message_raw fails. + self.send_message_raw(msg); + } + + fn send_message_raw(&mut self, msg: &Message) { + let (ch, ipc_ch) = match msg.channel { + 1 => (0, 1), // control + 2 => (1, 3), // data + _ => unreachable!(), + }; + + // allocate a msg. + let idx = self.find_free_message(ch).unwrap(); // TODO handle list full + + debug!("tx seq {} msg: {:?}", self.tx_seq_no, msg); + + let msg_slot = unsafe { addr_of_mut!((*self.cb).msgs[ch][idx]) }; + unsafe { msg_slot.write_volatile(*msg) } + let list_item = unsafe { addr_of_mut!((*self.cb).lists[ch].items[idx]) }; + unsafe { addr_of_mut!((*list_item).message).write_volatile(msg_slot) } + unsafe { addr_of_mut!((*list_item).state).write_volatile((self.tx_seq_no as u32) << 16 | 0x01) } + self.tx_seq_no = self.tx_seq_no.wrapping_add(1); + + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + ipc.tasks_send[ipc_ch].write(|w| unsafe { w.bits(1) }); + } + + fn handle_control(&mut self, msg: &Message) { + match msg.id >> 16 { + 1 => debug!("control msg: modem ready"), + 2 => self.handle_control_free(msg.data), + _ => warn!("unknown control message id {:08x}", msg.id), + } + } + + fn handle_control_free(&mut self, ptr: *mut u8) { + let base = unsafe { addr_of!((*self.cb).tx_bufs) } as usize; + let ptr = ptr as usize; + + if ptr < base { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + let diff = ptr - base; + let idx = diff / TX_BUF_SIZE; + + if idx >= TX_BUF_COUNT || idx * TX_BUF_SIZE != diff { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + trace!("control free pointer {:08x} idx {}", ptr, idx); + if !self.tx_buf_used[idx] { + warn!( + "control free pointer {:08x} idx {}: buffer was already free??", + ptr, idx + ); + } + self.tx_buf_used[idx] = false; + } + + fn handle_data(&mut self, msg: &Message, ch: &mut ch::Runner) { + if !msg.data.is_null() { + self.rx_check.check_length(msg.data, msg.data_len); + } + + let freed = match msg.id & 0xFFFF { + // AT + 3 => { + match msg.id >> 16 { + // AT request ack + 2 => false, + // AT response + 3 => self.handle_resp(msg), + // AT notification + 4 => false, + x => { + warn!("received unknown AT kind {}", x); + false + } + } + } + // IP + 4 => { + match msg.id >> 28 { + // IP response + 8 => self.handle_resp(msg), + // IP notification + 9 => match (msg.id >> 16) & 0xFFF { + // IP receive notification + 1 => { + if let Some(buf) = ch.try_rx_buf() { + let mut len = msg.data_len; + if len > buf.len() { + warn!("truncating rx'd packet from {} to {} bytes", len, buf.len()); + len = buf.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, buf.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + ch.rx_done(len); + } + false + } + _ => false, + }, + x => { + warn!("received unknown IP kind {}", x); + false + } + } + } + x => { + warn!("received unknown kind {}", x); + false + } + }; + + if !freed { + self.send_free(msg); + } + } + + fn handle_resp(&mut self, msg: &Message) -> bool { + let req_serial = u32::from_le_bytes(msg.param[0..4].try_into().unwrap()); + if req_serial == 0 { + return false; + } + + for optr in &mut self.requests { + if let Some(r) = optr { + if r.req_serial == req_serial { + let r = optr.take().unwrap(); + unsafe { r.resp_msg.write(*msg) } + r.waker.wake(); + *optr = None; + return true; + } + } + } + + warn!( + "resp with id {} serial {} doesn't match any pending req", + msg.id, req_serial + ); + false + } + + fn send_free(&mut self, msg: &Message) { + if msg.data.is_null() { + return; + } + + let mut free_msg: Message = unsafe { mem::zeroed() }; + free_msg.channel = 1; // control + free_msg.id = 0x20001; // free + free_msg.data = msg.data; + free_msg.data_len = msg.data_len; + + self.send_message_raw(&free_msg); + } +} + +struct PointerChecker { + start: *mut u8, + end: *mut u8, +} + +impl PointerChecker { + // check the pointer is in bounds in the arena, panic otherwise. + fn check_length(&self, ptr: *const u8, len: usize) { + assert!(ptr as usize >= self.start as usize); + let end_ptr = (ptr as usize).checked_add(len).unwrap(); + assert!(end_ptr <= self.end as usize); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check(&self, ptr: *const T) { + assert!(ptr.is_aligned()); + self.check_length(ptr as *const u8, mem::size_of::()); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_read(&self, ptr: *const T) -> T { + self.check(ptr); + unsafe { ptr.read_volatile() } + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_mut(&self, ptr: *mut T) { + self.check(ptr as *const T) + } +} + +/// Control handle for the driver. +/// +/// You can use this object to control the modem at runtime, such as running AT commands. +pub struct Control<'a> { + state: &'a RefCell, +} + +impl<'a> Control<'a> { + /// Wait for modem IPC to be initialized. + pub async fn wait_init(&self) { + poll_fn(|cx| { + let mut state = self.state.borrow_mut(); + if state.init { + return Poll::Ready(()); + } + state.init_waker.register(cx.waker()); + Poll::Pending + }) + .await + } + + async fn request(&self, msg: &mut Message, req_data: &[u8], resp_data: &mut [u8]) -> usize { + // get waker + let waker = poll_fn(|cx| Poll::Ready(cx.waker().clone())).await; + + // Send request + let mut state = self.state.borrow_mut(); + let mut req_serial = state.next_req_serial; + if msg.id & 0xFFFF == 3 { + // AT response seems to keep only the lower 8 bits. Others do keep the full 32 bits..?? + req_serial &= 0xFF; + } + + // increment next_req_serial, skip zero because we use it as an "ignore" value. + // We have to skip when the *lowest byte* is zero because AT responses. + state.next_req_serial = state.next_req_serial.wrapping_add(1); + if state.next_req_serial & 0xFF == 0 { + state.next_req_serial = state.next_req_serial.wrapping_add(1); + } + + msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes()); + state.send_message(msg, req_data); + + // Setup the pending request state. + let (req_slot_idx, req_slot) = state + .requests + .iter_mut() + .enumerate() + .find(|(_, x)| x.is_none()) + .unwrap(); + msg.id = 0; // zero out id, so when it becomes nonzero we know the req is done. + let msg_ptr: *mut Message = msg; + *req_slot = Some(PendingRequest { + req_serial, + resp_msg: msg_ptr, + waker, + }); + + drop(state); // don't borrow state across awaits. + + // On cancel, unregister the request slot. + let _drop = OnDrop::new(|| { + // Remove request slot. + let mut state = self.state.borrow_mut(); + let slot = &mut state.requests[req_slot_idx]; + if let Some(s) = slot { + if s.req_serial == req_serial { + *slot = None; + } + } + + // If cancelation raced with actually receiving the response, + // we own the data, so we have to free it. + let msg = unsafe { &mut *msg_ptr }; + if msg.id != 0 { + state.send_free(msg); + } + }); + // Wait for response. + poll_fn(|_| { + // we have to use the raw pointer and not the original reference `msg` + // because that'd invalidate the raw ptr that's still stored in `req_slot`. + if unsafe { (*msg_ptr).id } != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + _drop.defuse(); + + if msg.data.is_null() { + // no response data. + return 0; + } + + // Copy response data out, if any. + // Pointer was validated in StateInner::handle_data(). + let mut len = msg.data_len; + if len > resp_data.len() { + warn!("truncating response data from {} to {}", len, resp_data.len()); + len = resp_data.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, resp_data.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + self.state.borrow_mut().send_free(msg); + len + } + + /// Run an AT command. + /// + /// The response is written in `resp` and its length returned. + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x0001_0003; // AT command + msg.param_len = 4; + + self.request(&mut msg, req, resp).await + } + + /// Open the raw socket used for sending/receiving IP packets. + /// + /// This must be done after `AT+CFUN=1` (?) + pub async fn open_raw_socket(&self) { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7001_0004; // open socket + msg.param_len = 20; + + let param = [ + 0xFF, 0xFF, 0xFF, 0xFF, // req_serial + 0xFF, 0xFF, 0xFF, 0xFF, // ??? + 0x05, 0x00, 0x00, 0x00, // family + 0x03, 0x00, 0x00, 0x00, // type + 0x00, 0x00, 0x00, 0x00, // protocol + ]; + msg.param[..param.len()].copy_from_slice(¶m); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80010004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + assert_eq!(msg.param_len, 16); + let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap()); + debug!("got FD: {}", fd); + } +} + +/// Background runner for the driver. +pub struct Runner<'a, TW: embedded_io::Write> { + ch: ch::Runner<'a, MTU>, + state: &'a RefCell, + trace_writer: TW, +} + +impl<'a, TW: embedded_io::Write> Runner<'a, TW> { + /// Run the driver operation in the background. + /// + /// You must run this in a background task, concurrently with all network operations. + pub async fn run(mut self) -> ! { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let mut state = self.state.borrow_mut(); + state.poll(&mut self.trace_writer, &mut self.ch); + + if let Poll::Ready(buf) = self.ch.poll_tx_buf(cx) { + let fd = 128u32; // TODO unhardcode + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7006_0004; // IP send + msg.param_len = 12; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + state.send_message(&mut msg, buf); + self.ch.tx_done(); + } + + Poll::Pending + }) + .await + } +} + +const LIST_LEN: usize = 16; + +#[repr(C)] +struct ControlBlock { + version: u32, + rx_base: *mut u8, + rx_size: usize, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + modem_info_ptr: *mut ModemInfo, + trace_ptr: *mut Trace, + unk: u32, + + modem_info: ModemInfo, + trace: Trace, + + // 0 = control, 1 = data + lists: [List; 2], + msgs: [[Message; LIST_LEN]; 2], + + tx_bufs: [[u8; TX_BUF_SIZE]; TX_BUF_COUNT], +} + +#[repr(C)] +struct ModemInfo { + version: u32, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + padding: [u32; 5], +} + +#[repr(C)] +struct Trace { + size: usize, + base: *mut u8, + tx_state: u32, + tx_ptr: *mut u8, + rx_state: u32, + rx_ptr: *mut u8, + unk1: u32, + unk2: u32, +} + +const TRACE_CHANNEL_COUNT: usize = 3; + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceContext { + unk1: u32, + unk2: u32, + len: u32, + chans: [*mut TraceChannel; TRACE_CHANNEL_COUNT], +} + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceChannel { + id: u8, + unk1: u8, + unk2: u8, + unk3: u8, + write_ptr: *mut u8, + read_ptr: *mut u8, + start: *mut u8, + end: *mut u8, +} + +#[repr(C)] +struct List { + len: usize, + items: [ListItem; LIST_LEN], +} + +#[repr(C)] +struct ListItem { + /// top 16 bits: seqno + /// bottom 8 bits: + /// 0x01: sent + /// 0x02: held + /// 0x03: freed + state: u32, + message: *mut Message, +} + +#[repr(C)] +#[derive(defmt::Format, Clone, Copy)] +struct Message { + id: u32, + + /// 1 = control, 2 = data + channel: u8, + unk1: u8, + unk2: u8, + unk3: u8, + + data: *mut u8, + data_len: usize, + param_len: usize, + param: [u8; 44], +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/examples/nrf9160/.cargo/config.toml b/examples/nrf9160/.cargo/config.toml index f64c63966..6072b8595 100644 --- a/examples/nrf9160/.cargo/config.toml +++ b/examples/nrf9160/.cargo/config.toml @@ -1,5 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs run --chip nRF9160_xxAA" +# runner = "probe-rs run --chip nRF9160_xxAA" +runner = [ "probe-rs", "run", "--chip=nRF9160_xxAA", "--always-print-stacktrace", "--log-format={t} {[{L}]%bold} {s} {{c} {ff}:{l:1}%dimmed}" ] [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index c30b54ebd..fc24e99d2 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml @@ -8,6 +8,8 @@ license = "MIT OR Apache-2.0" embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] } defmt = "0.3" defmt-rtt = "0.4" @@ -15,6 +17,9 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } +static_cell = { version = "2" } +embedded-io = "0.6.1" +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } [profile.release] debug = 2 diff --git a/examples/nrf9160/memory.x b/examples/nrf9160/memory.x index 4c7d4ebf0..e33498773 100644 --- a/examples/nrf9160/memory.x +++ b/examples/nrf9160/memory.x @@ -1,5 +1,9 @@ MEMORY { - FLASH : ORIGIN = 0x00000000, LENGTH = 1024K - RAM : ORIGIN = 0x20018000, LENGTH = 160K + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20010000, LENGTH = 192K + IPC : ORIGIN = 0x20000000, LENGTH = 64K } + +PROVIDE(__start_ipc = ORIGIN(IPC)); +PROVIDE(__end_ipc = ORIGIN(IPC) + LENGTH(IPC)); diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs new file mode 100644 index 000000000..b1dac18a1 --- /dev/null +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -0,0 +1,250 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; +use core::ptr::addr_of_mut; +use core::str::FromStr; +use core::{slice, str}; + +use defmt::{assert, *}; +use embassy_executor::Spawner; +use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net_nrf91::{Runner, State}; +use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; +use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; +use embassy_nrf::uarte::Baudrate; +use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte}; +use embassy_time::{Duration, Timer}; +use embedded_io_async::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[interrupt] +fn IPC() { + embassy_net_nrf91::on_ipc_irq(); +} + +bind_interrupts!(struct Irqs { + UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler; +}); + +// embassy-net-nrf91 only supports blocking trace write for now. +// We don't want to block packet processing with slow uart writes, so +// we make an adapter that writes whatever fits in the buffer and drops +// data if it's full. +struct TraceWriter(BufferedUarteTx<'static, peripherals::SERIAL0>); + +impl embedded_io::ErrorType for TraceWriter { + type Error = core::convert::Infallible; +} + +impl embedded_io::Write for TraceWriter { + fn write(&mut self, buf: &[u8]) -> Result { + let _ = self.0.try_write(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[embassy_executor::task] +async fn modem_task(runner: Runner<'static, TraceWriter>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn blink_task(pin: AnyPin) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + loop { + led.set_high(); + Timer::after_millis(100).await; + led.set_low(); + Timer::after_millis(100).await; + } +} + +extern "C" { + static __start_ipc: u8; + static __end_ipc: u8; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Hello World!"); + + unwrap!(spawner.spawn(blink_task(p.P0_02.degrade()))); + + let ipc_mem = unsafe { + let ipc_start = &__start_ipc as *const u8 as *mut MaybeUninit; + let ipc_end = &__end_ipc as *const u8 as *mut MaybeUninit; + let ipc_len = ipc_end.offset_from(ipc_start) as usize; + slice::from_raw_parts_mut(ipc_start, ipc_len) + }; + + static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; + let mut config = uarte::Config::default(); + config.baudrate = Baudrate::BAUD1M; + let trace_writer = TraceWriter(BufferedUarteTx::new( + //let trace_uart = BufferedUarteTx::new( + unsafe { peripherals::SERIAL0::steal() }, + Irqs, + unsafe { peripherals::P0_01::steal() }, + //unsafe { peripherals::P0_14::steal() }, + config, + unsafe { &mut *addr_of_mut!(TRACE_BUF) }, + )); + + static STATE: StaticCell = StaticCell::new(); + let (device, control, runner) = embassy_net_nrf91::new(STATE.init(State::new()), ipc_mem, trace_writer).await; + unwrap!(spawner.spawn(modem_task(runner))); + + let config = embassy_net::Config::default(); + + // Generate "random" seed. nRF91 has no RNG, TODO figure out something... + let seed = 123456; + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + static STACK: StaticCell>> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + device, + config, + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + + unwrap!(spawner.spawn(net_task(stack))); + + control.wait_init().await; + info!("INIT OK"); + + let mut buf = [0u8; 256]; + + let n = control.at_command(b"AT+CFUN?", &mut buf).await; + info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); + + let n = control + .at_command(b"AT+CGDCONT=0,\"IP\",\"iot.nat.es\"", &mut buf) + .await; + info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); + let n = control + .at_command(b"AT+CGAUTH=0,1,\"orange\",\"orange\"", &mut buf) + .await; + info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); + + let n = control.at_command(b"AT+CFUN=1", &mut buf).await; + info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); + + info!("waiting for attach..."); + loop { + Timer::after_millis(500).await; + let n = control.at_command(b"AT+CGATT?", &mut buf).await; + let mut res = &buf[..n]; + pop_prefix(&mut res, b"+CGATT: "); + let res = split_field(&mut res); + info!("AT resp field: '{}'", unsafe { str::from_utf8_unchecked(res) }); + if res == b"1" { + break; + } + } + + let n = control.at_command(b"AT+CGPADDR=0", &mut buf).await; + let mut res = &buf[..n]; + pop_prefix(&mut res, b"+CGPADDR: 0,"); + let ip = split_field(&mut res); + let ip = Ipv4Address::from_str(unsafe { str::from_utf8_unchecked(ip) }).unwrap(); + info!("IP: '{}'", ip); + + info!("============== OPENING SOCKET"); + control.open_raw_socket().await; + + stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(ip, 32), + gateway: None, + dns_servers: Default::default(), + })); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("83.51.182.206").unwrap(); + if let Err(e) = socket.connect((host_addr, 8000)).await { + warn!("connect error: {:?}", e); + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + + let msg = b"Hello world!\n"; + loop { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after_secs(1).await; + } + } +} + +fn is_whitespace(char: u8) -> bool { + match char { + b'\r' | b'\n' | b' ' => true, + _ => false, + } +} + +fn is_separator(char: u8) -> bool { + match char { + b',' | b'\r' | b'\n' | b' ' => true, + _ => false, + } +} + +fn split_field<'a>(data: &mut &'a [u8]) -> &'a [u8] { + while !data.is_empty() && is_whitespace(data[0]) { + *data = &data[1..]; + } + + if data.is_empty() { + return &[]; + } + + if data[0] == b'"' { + let data2 = &data[1..]; + let end = data2.iter().position(|&x| x == b'"').unwrap_or(data2.len()); + let field = &data2[..end]; + let mut rest = &data2[data2.len().min(end + 1)..]; + if rest.first() == Some(&b'\"') { + rest = &rest[1..]; + } + while !rest.is_empty() && is_separator(rest[0]) { + rest = &rest[1..]; + } + *data = rest; + field + } else { + let end = data.iter().position(|&x| is_separator(x)).unwrap_or(data.len()); + let field = &data[0..end]; + let rest = &data[data.len().min(end + 1)..]; + *data = rest; + field + } +} + +fn pop_prefix(data: &mut &[u8], prefix: &[u8]) { + assert!(data.len() >= prefix.len()); + assert!(&data[..prefix.len()] == prefix); + *data = &data[prefix.len()..]; +} From 11652ff5c7b139fd14ae270659181c65e59515dc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 25 Jun 2024 21:15:23 +0200 Subject: [PATCH 010/127] don't crash if tx buffers fill up. --- embassy-net-nrf91/src/lib.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index 62ade6dd3..70ad176da 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![doc = include_str!("../README.md")] #![warn(missing_docs)] +#![deny(unused_must_use)] // must be first mod fmt; @@ -244,6 +245,10 @@ struct PendingRequest { waker: Waker, } +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct NoFreeBufs; + struct StateInner { init: bool, init_waker: WakerRegistration, @@ -450,13 +455,13 @@ impl StateInner { return None; } - fn send_message(&mut self, msg: &mut Message, data: &[u8]) { + fn send_message(&mut self, msg: &mut Message, data: &[u8]) -> Result<(), NoFreeBufs> { if data.is_empty() { msg.data = ptr::null_mut(); msg.data_len = 0; } else { assert!(data.len() <= TX_BUF_SIZE); - let buf_idx = self.find_free_tx_buf().unwrap(); // TODO handle out of bufs + let buf_idx = self.find_free_tx_buf().ok_or(NoFreeBufs)?; let buf = unsafe { addr_of_mut!((*self.cb).tx_bufs[buf_idx]) } as *mut u8; unsafe { copy_nonoverlapping(data.as_ptr(), buf, data.len()) } msg.data = buf; @@ -467,10 +472,10 @@ impl StateInner { } // TODO free data buf if send_message_raw fails. - self.send_message_raw(msg); + self.send_message_raw(msg) } - fn send_message_raw(&mut self, msg: &Message) { + fn send_message_raw(&mut self, msg: &Message) -> Result<(), NoFreeBufs> { let (ch, ipc_ch) = match msg.channel { 1 => (0, 1), // control 2 => (1, 3), // data @@ -478,7 +483,7 @@ impl StateInner { }; // allocate a msg. - let idx = self.find_free_message(ch).unwrap(); // TODO handle list full + let idx = self.find_free_message(ch).ok_or(NoFreeBufs)?; debug!("tx seq {} msg: {:?}", self.tx_seq_no, msg); @@ -491,6 +496,7 @@ impl StateInner { let ipc = unsafe { &*pac::IPC_NS::ptr() }; ipc.tasks_send[ipc_ch].write(|w| unsafe { w.bits(1) }); + Ok(()) } fn handle_control(&mut self, msg: &Message) { @@ -626,7 +632,7 @@ impl StateInner { free_msg.data = msg.data; free_msg.data_len = msg.data_len; - self.send_message_raw(&free_msg); + unwrap!(self.send_message_raw(&free_msg)); } } @@ -702,7 +708,7 @@ impl<'a> Control<'a> { } msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes()); - state.send_message(msg, req_data); + unwrap!(state.send_message(msg, req_data)); // Setup the pending request state. let (req_slot_idx, req_slot) = state @@ -838,7 +844,9 @@ impl<'a, TW: embedded_io::Write> Runner<'a, TW> { msg.id = 0x7006_0004; // IP send msg.param_len = 12; msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); - state.send_message(&mut msg, buf); + if let Err(e) = state.send_message(&mut msg, buf) { + warn!("tx failed: {:?}", e); + } self.ch.tx_done(); } From bc67cc22aa1461e6bce19b663b00bdccc818a998 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 5 Aug 2024 12:09:29 +0200 Subject: [PATCH 011/127] update driver channel dependency --- embassy-net-nrf91/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml index 2156346a4..e2d596d59 100644 --- a/embassy-net-nrf91/Cargo.toml +++ b/embassy-net-nrf91/Cargo.toml @@ -22,7 +22,7 @@ nrf9160-pac = { version = "0.12.0" } embassy-time = { version = "0.3.1", path = "../embassy-time" } embassy-sync = { version = "0.6.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"} -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} heapless = "0.8" embedded-io = "0.6.1" From 86a45b47e529864ac5a203b4ad26b9ee62127a1e Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 5 Aug 2024 12:09:58 +0200 Subject: [PATCH 012/127] add at command file --- embassy-net-nrf91/src/at.rs | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 embassy-net-nrf91/src/at.rs diff --git a/embassy-net-nrf91/src/at.rs b/embassy-net-nrf91/src/at.rs new file mode 100644 index 000000000..04cbf3876 --- /dev/null +++ b/embassy-net-nrf91/src/at.rs @@ -0,0 +1,45 @@ +use crate::{Error, Control}; + +// Drives the control loop of the modem based on declarative configuration. +pub struct AtDriver<'a> { + control: Control<'a>, + config: Config, +} + +pub struct Config { + pub network: NetworkConfig, +} + +pub struct NetworkConfig { + pub apn: &'static str, + pub prot: AuthProtection, + pub userid: &'static str, + pub password: &'static str, +} + +#[repr(u8)] +pub enum AuthProtection { + None = 0, + Pap = 1, + Chap = 2, +} + +impl<'a> AtDriver<'a> { + pub async fn new(control: Control<'a>, config: Config) -> Result { + control.wait_init().await; + Ok(Self { + control, + config, + }) + } + + async fn setup(&self) -> Result<(), Error> { + + } + + pub fn run(&self, stack: Stack>) -> ! { + loop { + + } + } +} From c9ad897d4a6f1c66d77372fd27ec135e0c798f65 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 21 Aug 2024 18:12:59 +0200 Subject: [PATCH 013/127] Use released bt-hci crate --- cyw43/Cargo.toml | 2 +- examples/rp/Cargo.toml | 1 - examples/rp23/Cargo.toml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index 9b469c338..751e59a69 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -36,7 +36,7 @@ heapless = "0.8.0" # Bluetooth deps embedded-io-async = { version = "0.6.0", optional = true } -bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3", optional = true, default-features = false } +bt-hci = { version = "0.1.0", optional = true } [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 83d5792b6..04b4c6317 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -74,7 +74,6 @@ opt-level = "z" [patch.crates-io] trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } -bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3" } embassy-executor = { path = "../../embassy-executor" } embassy-sync = { path = "../../embassy-sync" } embassy-futures = { path = "../../embassy-futures" } diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml index 8f8d6ff10..087f6fd69 100644 --- a/examples/rp23/Cargo.toml +++ b/examples/rp23/Cargo.toml @@ -71,7 +71,6 @@ opt-level = "z" [patch.crates-io] trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } -bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3" } embassy-executor = { path = "../../embassy-executor" } embassy-sync = { path = "../../embassy-sync" } embassy-futures = { path = "../../embassy-futures" } From acc26a076a44c4c64b960e259bb516a9bd636dfd Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 23 Aug 2024 15:04:00 +0200 Subject: [PATCH 014/127] embassy-net/read document return value Ok(0) --- embassy-net/src/tcp.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 18200287e..62ee5cb66 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -79,6 +79,9 @@ impl<'a> TcpReader<'a> { /// /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. + /// + /// A return value of Ok(0) means that the socket was closed and is longer able to + /// accept bytes or that the buffer provided is empty. pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } @@ -273,6 +276,9 @@ impl<'a> TcpSocket<'a> { /// /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. + /// + /// A return value of Ok(0) means that the socket was closed and is longer able to + /// accept bytes or that the buffer provided is empty. pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } From 5479647962477a794dfcde19d8e865eb47150caf Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 23 Aug 2024 19:16:33 +0200 Subject: [PATCH 015/127] embassy-net: fix/clearify TcpReader docs. Expand docs on timeouts --- embassy-net/src/tcp.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 62ee5cb66..b2e3279cc 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -80,8 +80,14 @@ impl<'a> TcpReader<'a> { /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. /// - /// A return value of Ok(0) means that the socket was closed and is longer able to - /// accept bytes or that the buffer provided is empty. + /// # Note + /// A return value of Ok(0) means that we have read all data and the remote + /// side has closed our receive half of the socket. The remote can no longer + /// send bytes. + /// + /// The send half of the socket is still open. If you want to reconnect using + /// the socket you split this reader off the send half needs to be closed using + /// [`abort()`](TcpSocket::abort). pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } @@ -277,8 +283,8 @@ impl<'a> TcpSocket<'a> { /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. /// - /// A return value of Ok(0) means that the socket was closed and is longer able to - /// accept bytes or that the buffer provided is empty. + /// A return value of Ok(0) means that the socket was closed and is longer + /// able to receive any data. pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } @@ -303,6 +309,10 @@ impl<'a> TcpSocket<'a> { /// /// If the timeout is set, the socket will be closed if no data is received for the /// specified duration. + /// + /// # Note: + /// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when + /// the remote could still respond. pub fn set_timeout(&mut self, duration: Option) { self.io .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) @@ -314,6 +324,9 @@ impl<'a> TcpSocket<'a> { /// the specified duration of inactivity. /// /// If not set, the socket will not send keep-alive packets. + /// + /// By setting a [`timeout`](Self::timeout) larger then the keep alive you + /// can detect a remote endpoint that no longer answers. pub fn set_keep_alive(&mut self, interval: Option) { self.io .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) From 8c1024b2a59c82ffccaba0327c02bc99da047943 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 24 Aug 2024 12:11:54 +0200 Subject: [PATCH 016/127] Set up timer0 tick when initializing clocks --- embassy-rp/src/clocks.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 5f7ba10e2..ed146844c 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -512,12 +512,18 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_int(config.ref_clk.div); }); - // Configure tick generation on the 2040. On the 2350 the timers are driven from the sysclk. + // Configure tick generation on the 2040. #[cfg(feature = "rp2040")] pac::WATCHDOG.tick().write(|w| { w.set_cycles((clk_ref_freq / 1_000_000) as u16); w.set_enable(true); }); + // Configure tick generator on the 2350 + #[cfg(feature = "_rp235x")] + { + pac::TICKS.timer0_cycles().write(|w| w.0 = clk_ref_freq / 1_000_000); + pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true)); + } let (sys_src, sys_aux, clk_sys_freq) = { use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; From 87e97fb69d31606456ddbf9e3308364773648929 Mon Sep 17 00:00:00 2001 From: elagil Date: Sat, 24 Aug 2024 20:16:00 +0200 Subject: [PATCH 017/127] feat: add function to check if SAI is muted --- embassy-stm32/src/sai/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index c48d81b5f..6bf184dd8 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -987,6 +987,21 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { ch.cr2().modify(|w| w.set_mute(value)); } + /// Determine the mute state of the receiver. + /// + /// Clears the mute state flag in the status register. + pub fn is_muted(&self) -> Result { + match &self.ring_buffer { + RingBuffer::Readable(_) => { + let ch = T::REGS.ch(self.sub_block as usize); + let mute_state = ch.sr().read().mutedet(); + ch.clrfr().write(|w| w.set_cmutedet(true)); + Ok(mute_state) + } + _ => Err(Error::NotAReceiver), + } + } + /// Write data to the SAI ringbuffer. /// /// This appends the data to the buffer and returns immediately. The From 557cff708505eb02c2b4c7f264a726bb9c17812a Mon Sep 17 00:00:00 2001 From: elagil Date: Sat, 24 Aug 2024 20:23:10 +0200 Subject: [PATCH 018/127] feat: Add support for a full-speed ULPI mode --- embassy-stm32/src/usb/otg.rs | 49 +++++++++++++++++++++++++++++ embassy-usb-synopsys-otg/src/lib.rs | 7 +++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index 8ee8dcc36..e27b164e4 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -97,6 +97,55 @@ impl<'d, T: Instance> Driver<'d, T> { } } + /// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode). + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs_ulpi( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ulpi_clk: impl Peripheral

> + 'd, + ulpi_dir: impl Peripheral

> + 'd, + ulpi_nxt: impl Peripheral

> + 'd, + ulpi_stp: impl Peripheral

> + 'd, + ulpi_d0: impl Peripheral

> + 'd, + ulpi_d1: impl Peripheral

> + 'd, + ulpi_d2: impl Peripheral

> + 'd, + ulpi_d3: impl Peripheral

> + 'd, + ulpi_d4: impl Peripheral

> + 'd, + ulpi_d5: impl Peripheral

> + 'd, + ulpi_d6: impl Peripheral

> + 'd, + ulpi_d7: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + config_ulpi_pins!( + ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, + ulpi_d7 + ); + + let regs = T::regs(); + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::ExternalFullSpeed, + quirk_setup_late_cnak: quirk_setup_late_cnak(regs), + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + /// Initializes USB OTG peripheral with external High-Speed PHY. /// /// # Arguments diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index b90e059f6..82752010a 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -179,6 +179,8 @@ pub enum PhyType { /// /// Available on a few STM32 chips. InternalHighSpeed, + /// External ULPI Full-Speed PHY (or High-Speed PHY in Full-Speed mode) + ExternalFullSpeed, /// External ULPI High-Speed PHY ExternalHighSpeed, } @@ -188,14 +190,14 @@ impl PhyType { pub fn internal(&self) -> bool { match self { PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, - PhyType::ExternalHighSpeed => false, + PhyType::ExternalHighSpeed | PhyType::ExternalFullSpeed => false, } } /// Get whether this PHY is any of the high-speed types. pub fn high_speed(&self) -> bool { match self { - PhyType::InternalFullSpeed => false, + PhyType::InternalFullSpeed | PhyType::ExternalFullSpeed => false, PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, } } @@ -204,6 +206,7 @@ impl PhyType { match self { PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalFullSpeed => vals::Dspd::FULL_SPEED_EXTERNAL, PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, } } From 41e162541aa56553162932278ba8b6763fb63ecd Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 25 Aug 2024 07:14:19 +0200 Subject: [PATCH 019/127] stm32: Fix log storm when no CAN is connected Running the bxcan driver without having it connected to a CAN bus causes the `info` logs to bombard. This removes the logging statements as they looked like remnants from the development of the driver. --- embassy-stm32/src/can/bxcan/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index 278c93ff4..baa4bee79 100644 --- a/embassy-stm32/src/can/bxcan/mod.rs +++ b/embassy-stm32/src/can/bxcan/mod.rs @@ -68,7 +68,6 @@ pub struct SceInterruptHandler { impl interrupt::typelevel::Handler for SceInterruptHandler { unsafe fn on_interrupt() { - info!("sce irq"); let msr = T::regs().msr(); let msr_val = msr.read(); @@ -76,9 +75,8 @@ impl interrupt::typelevel::Handler for SceInterrup msr.modify(|m| m.set_slaki(true)); T::state().err_waker.wake(); } else if msr_val.erri() { - info!("Error interrupt"); // Disable the interrupt, but don't acknowledge the error, so that it can be - // forwarded off the the bus message consumer. If we don't provide some way for + // forwarded off the bus message consumer. If we don't provide some way for // downstream code to determine that it has already provided this bus error instance // to the bus message consumer, we are doomed to re-provide a single error instance for // an indefinite amount of time. From bea1f34440eff7842c0c04e8b7381cdb429f5005 Mon Sep 17 00:00:00 2001 From: Daniel Trnka Date: Wed, 21 Aug 2024 13:15:48 +0200 Subject: [PATCH 020/127] stm32/usart: sending break character --- embassy-stm32/src/usart/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 89d92dda2..cbd4ac3bc 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -520,6 +520,21 @@ impl<'d, M: Mode> UartTx<'d, M> { pub fn blocking_flush(&mut self) -> Result<(), Error> { blocking_flush(self.info) } + + /// Send break character + pub fn send_break(&self) { + // Busy wait until previous break has been sent + #[cfg(any(usart_v1, usart_v2))] + while self.info.regs.cr1().read().sbk() {} + #[cfg(any(usart_v3, usart_v4))] + while self.info.regs.isr().read().sbkf() {} + + // Send break right after completing the current character transmission + #[cfg(any(usart_v1, usart_v2))] + self.info.regs.cr1().modify(|w| w.set_sbk(true)); + #[cfg(any(usart_v3, usart_v4))] + self.info.regs.rqr().write(|w| w.set_sbkrq(true)); + } } fn blocking_flush(info: &Info) -> Result<(), Error> { @@ -1365,6 +1380,11 @@ impl<'d, M: Mode> Uart<'d, M> { pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { (self.tx, self.rx) } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break(); + } } fn reconfigure(info: &Info, kernel_clock: Hertz, config: &Config) -> Result<(), ConfigError> { From 9b142bd80fad43beeb1114ec289f570310aae2de Mon Sep 17 00:00:00 2001 From: Cirrus Date: Sun, 25 Aug 2024 13:14:36 -0700 Subject: [PATCH 021/127] feat(embassy-net): add zero-copy UDP send/recv functions Added recv_from_with and send_to_with. These are conceptually similar to TCP's read_with and write_with functions. An application can parse received datagrams directly out of the receive buffer or assemble a datagram of known-length directly into the send buffer. --- embassy-net/src/udp.rs | 63 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 6e50c4e01..1d5360187 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -138,6 +138,35 @@ impl<'a> UdpSocket<'a> { }) } + /// Receive a datagram with a zero-copy function. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will call the provided function + /// with the number of bytes received and the remote endpoint and return + /// `Poll::Ready` with the function's returned value. + pub async fn recv_from_with(&mut self, f: F) -> R + where + F: FnOnce(&[u8], UdpMetadata) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.recv() { + Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)), + Err(udp::RecvError::Truncated) => unreachable!(), + Err(udp::RecvError::Exhausted) => { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + } + }) + }) + .await + } + /// Send a datagram to the specified remote endpoint. /// /// This method will wait until the datagram has been sent. @@ -181,6 +210,40 @@ impl<'a> UdpSocket<'a> { }) } + /// Send a datagram to the specified remote endpoint with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into + Copy, + F: FnOnce(&mut [u8]) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.send(size, remote_endpoint) { + Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + } + }) + }) + .await + } + /// Returns the local endpoint of the socket. pub fn endpoint(&self) -> IpListenEndpoint { self.with(|s, _| s.endpoint()) From 0a33edc9976caddcdebe0943cce78376f9c49789 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 26 Aug 2024 09:41:52 -0400 Subject: [PATCH 022/127] Import rom_data for the rp235x, don't use intrinsics on rp235x Many thanks to @thejpster for his work on the rom_data! Working around boot2 is currently a bit hacky for the rp235x, that will improve in upcoming rp235x flash pr. --- embassy-rp/src/flash.rs | 82 +- embassy-rp/src/lib.rs | 1 + embassy-rp/src/rom_data/mod.rs | 33 + .../src/{rom_data.rs => rom_data/rp2040.rs} | 4 +- embassy-rp/src/rom_data/rp235x.rs | 752 ++++++++++++++++++ 5 files changed, 850 insertions(+), 22 deletions(-) create mode 100644 embassy-rp/src/rom_data/mod.rs rename embassy-rp/src/{rom_data.rs => rom_data/rp2040.rs} (99%) create mode 100644 embassy-rp/src/rom_data/rp235x.rs diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index a68493932..5f7922f8e 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -510,11 +510,19 @@ mod ram_helpers { /// /// `addr` and `len` parameters must be valid and are not checked. pub unsafe fn flash_range_erase(addr: u32, len: u32) { + #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(true, false, &boot2) - } else { + let ptrs = { + #[cfg(feature = "rp2040")] + { + if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(true, false, &boot2) + } else { + flash_function_pointers(true, false) + } + } + #[cfg(feature = "_rp235x")] flash_function_pointers(true, false) }; @@ -540,11 +548,19 @@ mod ram_helpers { /// /// `addr` and `len` parameters must be valid and are not checked. pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) { + #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(true, true, &boot2) - } else { + let ptrs = { + #[cfg(feature = "rp2040")] + { + if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(true, true, &boot2) + } else { + flash_function_pointers(true, true) + } + } + #[cfg(feature = "_rp235x")] flash_function_pointers(true, true) }; @@ -575,11 +591,19 @@ mod ram_helpers { /// /// `addr` and `len` parameters must be valid and are not checked. pub unsafe fn flash_range_program(addr: u32, data: &[u8]) { + #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(false, true, &boot2) - } else { + let ptrs = { + #[cfg(feature = "rp2040")] + { + if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, true, &boot2) + } else { + flash_function_pointers(false, true) + } + } + #[cfg(feature = "_rp235x")] flash_function_pointers(false, true) }; @@ -708,13 +732,22 @@ mod ram_helpers { /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) pub unsafe fn flash_unique_id(out: &mut [u8]) { + #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(false, false, &boot2) - } else { + let ptrs = { + #[cfg(feature = "rp2040")] + { + if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + } + } + #[cfg(feature = "_rp235x")] flash_function_pointers(false, false) }; + // 4B - read unique ID let cmd = [0x4B]; read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers); @@ -736,13 +769,22 @@ mod ram_helpers { /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) pub unsafe fn flash_jedec_id() -> u32 { + #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(false, false, &boot2) - } else { + let ptrs = { + #[cfg(feature = "rp2040")] + { + if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + } + } + #[cfg(feature = "_rp235x")] flash_function_pointers(false, false) }; + let mut id = [0u8; 4]; // 9F - read JEDEC ID let cmd = [0x9F]; diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 21f0771de..c2c57eaa0 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -15,6 +15,7 @@ pub use rp_binary_info as binary_info; #[cfg(feature = "critical-section-impl")] mod critical_section_impl; +#[cfg(feature = "rp2040")] mod intrinsics; pub mod adc; diff --git a/embassy-rp/src/rom_data/mod.rs b/embassy-rp/src/rom_data/mod.rs new file mode 100644 index 000000000..e5fcf8e3c --- /dev/null +++ b/embassy-rp/src/rom_data/mod.rs @@ -0,0 +1,33 @@ +#![cfg_attr( + feature = "rp2040", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. +" +)] +#![cfg_attr( + feature = "_rp235x", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device +" +)] + +#[cfg_attr(feature = "rp2040", path = "rp2040.rs")] +#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")] +mod inner; +pub use inner::*; diff --git a/embassy-rp/src/rom_data.rs b/embassy-rp/src/rom_data/rp2040.rs similarity index 99% rename from embassy-rp/src/rom_data.rs rename to embassy-rp/src/rom_data/rp2040.rs index baebe5b6c..5a74eddd6 100644 --- a/embassy-rp/src/rom_data.rs +++ b/embassy-rp/src/rom_data/rp2040.rs @@ -189,7 +189,7 @@ macro_rules! rom_functions { declare_rom_function! { $(#[$outer])* fn $name( $($argname: $ty),* ) -> $ret { - $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) } } @@ -205,7 +205,7 @@ macro_rules! rom_functions { declare_rom_function! { $(#[$outer])* unsafe fn $name( $($argname: $ty),* ) -> $ret { - $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) } } diff --git a/embassy-rp/src/rom_data/rp235x.rs b/embassy-rp/src/rom_data/rp235x.rs new file mode 100644 index 000000000..b16fee8f7 --- /dev/null +++ b/embassy-rp/src/rom_data/rp235x.rs @@ -0,0 +1,752 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootromā€™s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command ā€” this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API ā€” see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. This makes the runtime- to-storage address map an + /// identity map, i.e. the mapped and unmapped address are equal, and the + /// entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation ā€” erase, read, program + /// ā€” is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} From d4ab9fc247731e5f8ede4bb60a8c3c63136e1e6d Mon Sep 17 00:00:00 2001 From: James Bowes Date: Mon, 26 Aug 2024 10:33:13 -0300 Subject: [PATCH 023/127] chore: Remove unused keyboard code from rp mouse example The usb mouse example included code copied from the keyboard example to set up a button, which is not used in the mouse example. Remove it, to make the example clearer. --- examples/rp/src/bin/usb_hid_mouse.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/examples/rp/src/bin/usb_hid_mouse.rs b/examples/rp/src/bin/usb_hid_mouse.rs index cce344fb0..5ee650910 100644 --- a/examples/rp/src/bin/usb_hid_mouse.rs +++ b/examples/rp/src/bin/usb_hid_mouse.rs @@ -8,7 +8,6 @@ use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; -use embassy_rp::gpio::{Input, Pull}; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; use embassy_time::Timer; @@ -75,12 +74,6 @@ async fn main(_spawner: Spawner) { // Run the USB device. let usb_fut = usb.run(); - // Set up the signal pin that will be used to trigger the keyboard. - let mut signal_pin = Input::new(p.PIN_16, Pull::None); - - // Enable the schmitt trigger to slightly debounce. - signal_pin.set_schmitt(true); - let (reader, mut writer) = hid.split(); // Do stuff with the class! From 9347571fea243719826ff21b250bc0dff7f51fa5 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 26 Aug 2024 20:28:30 +0200 Subject: [PATCH 024/127] rp: add example code to flash bluetooth fw (#3290) --- examples/rp/src/bin/bluetooth.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/rp/src/bin/bluetooth.rs b/examples/rp/src/bin/bluetooth.rs index 901521b60..7524e7929 100644 --- a/examples/rp/src/bin/bluetooth.rs +++ b/examples/rp/src/bin/bluetooth.rs @@ -43,8 +43,10 @@ async fn main(spawner: Spawner) { // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + // probe-rs download 43439A0_btfw.bin --format bin --chip RP2040 --base-address 0x10141400 //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + //let btfw = unsafe { core::slice::from_raw_parts(0x10141400 as *const u8, 6164) }; let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); From f0a86070512ad739641cee7d9fa39d63f5c8a9f6 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 26 Aug 2024 19:45:57 +0100 Subject: [PATCH 025/127] Add block-device-driver impl for use with embedded-fatfs (#2607) --- embassy-stm32/Cargo.toml | 2 ++ embassy-stm32/src/sdmmc/mod.rs | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 54abd0799..9a6a5908e 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -88,6 +88,8 @@ static_assertions = { version = "1.1" } volatile-register = { version = "0.2.1" } bitflags = "2.4.2" +block-device-driver = { version = "0.2" } +aligned = "0.4.1" [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 44ff9fcd5..ed344c412 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -1511,3 +1511,44 @@ foreach_peripheral!( } }; ); + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> block_device_driver::BlockDevice<512> for Sdmmc<'d, T, Dma> { + type Error = Error; + type Align = aligned::A4; + + async fn read( + &mut self, + mut block_address: u32, + buf: &mut [aligned::Aligned], + ) -> Result<(), Self::Error> { + // FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time + for block in buf.iter_mut() { + // safety aligned by block device + let block = unsafe { &mut *(block as *mut _ as *mut crate::sdmmc::DataBlock) }; + self.read_block(block_address, block).await?; + block_address += 1; + } + + Ok(()) + } + + async fn write( + &mut self, + mut block_address: u32, + buf: &[aligned::Aligned], + ) -> Result<(), Self::Error> { + // FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time + for block in buf.iter() { + // safety aligned by block device + let block = unsafe { &*(block as *const _ as *const crate::sdmmc::DataBlock) }; + self.write_block(block_address, block).await?; + block_address += 1; + } + + Ok(()) + } + + async fn size(&mut self) -> Result { + Ok(self.card()?.size()) + } +} From b56e95bf7c2c5ab0ead23ee66de05cf9a288bc5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 28 Aug 2024 10:26:48 +0200 Subject: [PATCH 026/127] Fix a typo --- embassy-time-driver/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-time-driver/src/lib.rs b/embassy-time-driver/src/lib.rs index 565597935..aab2f626e 100644 --- a/embassy-time-driver/src/lib.rs +++ b/embassy-time-driver/src/lib.rs @@ -98,7 +98,7 @@ pub trait Driver: Send + Sync + 'static { /// /// Implementations MUST ensure that: /// - This is guaranteed to be monotonic, i.e. a call to now() will always return - /// a greater or equal value than earler calls. Time can't "roll backwards". + /// a greater or equal value than earlier calls. Time can't "roll backwards". /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say /// in 10_000 years (Human civilization is likely to already have self-destructed /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers From 22f4459ae28fe7e299f775f95952132d3c3dffa2 Mon Sep 17 00:00:00 2001 From: Daniel Trnka Date: Wed, 28 Aug 2024 21:35:31 +0200 Subject: [PATCH 027/127] stm32/usart: sending break character in buffered usart --- embassy-stm32/src/usart/buffered.rs | 14 ++++++++++++-- embassy-stm32/src/usart/mod.rs | 27 ++++++++++++++++----------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 06cc0e41d..86f56eb7c 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -12,8 +12,8 @@ use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(any(usart_v1, usart_v2)))] use super::DePin; use super::{ - clear_interrupt_flags, configure, rdr, reconfigure, sr, tdr, Config, ConfigError, CtsPin, Error, Info, Instance, - Regs, RtsPin, RxPin, TxPin, + clear_interrupt_flags, configure, rdr, reconfigure, send_break, sr, tdr, Config, ConfigError, CtsPin, Error, Info, + Instance, Regs, RtsPin, RxPin, TxPin, }; use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::interrupt::{self, InterruptExt}; @@ -359,6 +359,11 @@ impl<'d> BufferedUart<'d> { Ok(()) } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break() + } } impl<'d> BufferedUartRx<'d> { @@ -538,6 +543,11 @@ impl<'d> BufferedUartTx<'d> { Ok(()) } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } } impl<'d> Drop for BufferedUartRx<'d> { diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index cbd4ac3bc..e7f2f890a 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -523,17 +523,7 @@ impl<'d, M: Mode> UartTx<'d, M> { /// Send break character pub fn send_break(&self) { - // Busy wait until previous break has been sent - #[cfg(any(usart_v1, usart_v2))] - while self.info.regs.cr1().read().sbk() {} - #[cfg(any(usart_v3, usart_v4))] - while self.info.regs.isr().read().sbkf() {} - - // Send break right after completing the current character transmission - #[cfg(any(usart_v1, usart_v2))] - self.info.regs.cr1().modify(|w| w.set_sbk(true)); - #[cfg(any(usart_v3, usart_v4))] - self.info.regs.rqr().write(|w| w.set_sbkrq(true)); + send_break(&self.info.regs); } } @@ -549,6 +539,21 @@ fn blocking_flush(info: &Info) -> Result<(), Error> { Ok(()) } +/// Send break character +pub fn send_break(regs: &Regs) { + // Busy wait until previous break has been sent + #[cfg(any(usart_v1, usart_v2))] + while regs.cr1().read().sbk() {} + #[cfg(any(usart_v3, usart_v4))] + while regs.isr().read().sbkf() {} + + // Send break right after completing the current character transmission + #[cfg(any(usart_v1, usart_v2))] + regs.cr1().modify(|w| w.set_sbk(true)); + #[cfg(any(usart_v3, usart_v4))] + regs.rqr().write(|w| w.set_sbkrq(true)); +} + impl<'d> UartRx<'d, Async> { /// Create a new rx-only UART with no hardware flow control. /// From 372270a9b962196ede9c60a705cc3138ba592fec Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Tue, 27 Aug 2024 13:19:07 -0400 Subject: [PATCH 028/127] rp235x flash support. The 2350 doesn't have a boot2 like the 2040, but it does have the concept of a xip setup function that could be customized. By default the bootrom searches for the attached flash chip and provides an xip setup func at the base of the bootram. That bootram is not executable, so it still needs to be copied to ram like boot2 would be. Currently does not use inline assembly. Also switch to picotool, as elf2uf2 has not been patched to support the 2350. --- embassy-rp/src/flash.rs | 135 ++++++++++++++----------------- embassy-rp/src/lib.rs | 4 +- examples/rp23/.cargo/config.toml | 3 +- examples/rp23/src/bin/flash.rs | 15 +--- 4 files changed, 68 insertions(+), 89 deletions(-) diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index 5f7922f8e..dab99b4e2 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -17,9 +17,13 @@ use crate::peripherals::FLASH; /// Flash base address. pub const FLASH_BASE: *const u32 = 0x10000000 as _; +/// Address for xip setup function set up by the 235x bootrom. +#[cfg(feature = "_rp235x")] +pub const BOOTROM_BASE: *const u32 = 0x400e0000 as _; + /// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead. // TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance. -pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram"); +pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x"); // **NOTE**: // @@ -97,7 +101,10 @@ impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, ' // Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in // flight that might effect an address written to start a new transfer. This stalls // until after any transfer is complete, so the address will not change anymore. + #[cfg(feature = "rp2040")] const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _; unsafe { core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE); } @@ -225,12 +232,14 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI } /// Read SPI flash unique ID + #[cfg(feature = "rp2040")] pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; Ok(()) } /// Read SPI flash JEDEC ID + #[cfg(feature = "rp2040")] pub fn blocking_jedec_id(&mut self) -> Result { let mut jedec = None; unsafe { @@ -301,7 +310,10 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> { // Use the XIP AUX bus port, rather than the FIFO register access (e.x. // pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on // general XIP access. + #[cfg(feature = "rp2040")] const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _; let transfer = unsafe { crate::dma::read( self.dma.as_mut().unwrap(), @@ -510,19 +522,14 @@ mod ram_helpers { /// /// `addr` and `len` parameters must be valid and are not checked. pub unsafe fn flash_range_erase(addr: u32, len: u32) { - #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = { + let ptrs = if USE_BOOT2 { #[cfg(feature = "rp2040")] - { - if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(true, false, &boot2) - } else { - flash_function_pointers(true, false) - } - } + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(true, false, &boot2) + } else { flash_function_pointers(true, false) }; @@ -548,19 +555,14 @@ mod ram_helpers { /// /// `addr` and `len` parameters must be valid and are not checked. pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) { - #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = { + let ptrs = if USE_BOOT2 { #[cfg(feature = "rp2040")] - { - if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(true, true, &boot2) - } else { - flash_function_pointers(true, true) - } - } + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(true, true, &boot2) + } else { flash_function_pointers(true, true) }; @@ -591,19 +593,14 @@ mod ram_helpers { /// /// `addr` and `len` parameters must be valid and are not checked. pub unsafe fn flash_range_program(addr: u32, data: &[u8]) { - #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = { + let ptrs = if USE_BOOT2 { #[cfg(feature = "rp2040")] - { - if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(false, true, &boot2) - } else { - flash_function_pointers(false, true) - } - } + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(false, true, &boot2) + } else { flash_function_pointers(false, true) }; @@ -630,16 +627,7 @@ mod ram_helpers { #[link_section = ".data.ram_func"] #[cfg(feature = "rp2040")] unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { - /* - Should be equivalent to: - rom_data::connect_internal_flash(); - rom_data::flash_exit_xip(); - rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected - rom_data::flash_range_program(addr, data as *const _, len); // if selected - rom_data::flash_flush_cache(); - rom_data::flash_enter_cmd_xip(); - */ - #[cfg(target_arch = "arm")] + //#[cfg(target_arch = "arm")] core::arch::asm!( "mov r8, r0", "mov r9, r2", @@ -691,11 +679,30 @@ mod ram_helpers { ); } + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 #[inline(never)] #[link_section = ".data.ram_func"] #[cfg(feature = "_rp235x")] - unsafe fn write_flash_inner(_addr: u32, _len: u32, _data: Option<&[u8]>, _ptrs: *const FlashFunctionPointers) { - todo!(); + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()); + ((*ptrs).connect_internal_flash)(); + ((*ptrs).flash_exit_xip)(); + if (*ptrs).flash_range_erase.is_some() { + ((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0); + } + if (*ptrs).flash_range_program.is_some() { + ((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize); + } + ((*ptrs).flash_flush_cache)(); + ((*ptrs).flash_enter_cmd_xip)(); } #[repr(C)] @@ -731,20 +738,13 @@ mod ram_helpers { /// - DMA must not access flash memory /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] pub unsafe fn flash_unique_id(out: &mut [u8]) { - #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = { - #[cfg(feature = "rp2040")] - { - if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(false, false, &boot2) - } else { - flash_function_pointers(false, false) - } - } - #[cfg(feature = "_rp235x")] + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { flash_function_pointers(false, false) }; @@ -768,20 +768,13 @@ mod ram_helpers { /// - DMA must not access flash memory /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] pub unsafe fn flash_jedec_id() -> u32 { - #[cfg(feature = "rp2040")] let mut boot2 = [0u32; 256 / 4]; - let ptrs = { - #[cfg(feature = "rp2040")] - { - if USE_BOOT2 { - rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); - flash_function_pointers_with_boot2(false, false, &boot2) - } else { - flash_function_pointers(false, false) - } - } - #[cfg(feature = "_rp235x")] + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { flash_function_pointers(false, false) }; @@ -792,6 +785,7 @@ mod ram_helpers { u32::from_be_bytes(id) } + #[cfg(feature = "rp2040")] unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) { read_flash_inner( FlashCommand { @@ -932,13 +926,6 @@ mod ram_helpers { clobber_abi("C"), ); } - - #[inline(never)] - #[link_section = ".data.ram_func"] - #[cfg(feature = "_rp235x")] - unsafe fn read_flash_inner(_cmd: FlashCommand, _ptrs: *const FlashFunctionPointers) { - todo!(); - } } /// Make sure to uphold the contract points with rp2040-flash. diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index c2c57eaa0..f8fcfe52b 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -402,7 +402,7 @@ embassy_hal_internal::peripherals! { BOOTSEL, } -#[cfg(not(feature = "boot2-none"))] +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] macro_rules! select_bootloader { ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { $( @@ -419,7 +419,7 @@ macro_rules! select_bootloader { } } -#[cfg(not(feature = "boot2-none"))] +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] select_bootloader! { "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, diff --git a/examples/rp23/.cargo/config.toml b/examples/rp23/.cargo/config.toml index f77e004dc..9a92b1ce2 100644 --- a/examples/rp23/.cargo/config.toml +++ b/examples/rp23/.cargo/config.toml @@ -1,6 +1,7 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] #runner = "probe-rs run --chip RP2040" -runner = "elf2uf2-rs -d" +#runner = "elf2uf2-rs -d" +runner = "picotool load -u -v -x -t elf" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/rp23/src/bin/flash.rs b/examples/rp23/src/bin/flash.rs index 811561f26..84011e394 100644 --- a/examples/rp23/src/bin/flash.rs +++ b/examples/rp23/src/bin/flash.rs @@ -21,7 +21,7 @@ pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ embassy_rp::binary_info::rp_program_name!(c"example"), embassy_rp::binary_info::rp_cargo_version!(), - embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_description!(c"Flash"), embassy_rp::binary_info::rp_program_build_attribute!(), ]; @@ -41,22 +41,13 @@ async fn main(_spawner: Spawner) { let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH0); - // Get JEDEC id - let jedec = flash.blocking_jedec_id().unwrap(); - info!("jedec id: 0x{:x}", jedec); - - // Get unique id - let mut uid = [0; 8]; - flash.blocking_unique_id(&mut uid).unwrap(); - info!("unique id: {:?}", uid); - erase_write_sector(&mut flash, 0x00); multiwrite_bytes(&mut flash, ERASE_SIZE as u32); background_read(&mut flash, (ERASE_SIZE * 2) as u32).await; - loop {} + info!("Flash Works!"); } fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { @@ -82,7 +73,7 @@ fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); info!("Contents after write starts with {=[u8]}", read_buf[0..4]); - if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { + if read_buf[0..4] != [0x01, 0x02, 0x03, 0x04] { defmt::panic!("unexpected"); } } From abcb39a58b63c32e91b748d4380f4f6492fd28cb Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Thu, 29 Aug 2024 17:32:43 +0200 Subject: [PATCH 029/127] Allow bos_descriptor_buf to be a zero-length slice --- embassy-usb/src/descriptor.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index f1773fa8a..c4d79e39f 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -308,6 +308,9 @@ impl<'a> BosWriter<'a> { } pub(crate) fn bos(&mut self) { + if (self.writer.buf.len() - self.writer.position) < 5 { + return; + } self.num_caps_mark = Some(self.writer.position + 4); self.writer.write( descriptor_type::BOS, @@ -350,6 +353,9 @@ impl<'a> BosWriter<'a> { } pub(crate) fn end_bos(&mut self) { + if self.writer.position == 0 { + return; + } self.num_caps_mark = None; let position = self.writer.position as u16; self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes()); From 0434798439b9037a4fa5f30165879292e388f042 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 29 Aug 2024 12:28:23 -0400 Subject: [PATCH 030/127] Import otp from rp-hal, helper fns for chipid and randid Again, credit to @thejpster for doing the hard part and figuring out the otp. --- embassy-rp/src/lib.rs | 2 + embassy-rp/src/otp.rs | 108 +++++++++++++++++++++++++++++++++++ examples/rp23/src/bin/otp.rs | 46 +++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 embassy-rp/src/otp.rs create mode 100644 examples/rp23/src/bin/otp.rs diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index f8fcfe52b..c357c14c2 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -32,6 +32,8 @@ pub mod gpio; pub mod i2c; pub mod i2c_slave; pub mod multicore; +#[cfg(feature = "_rp235x")] +pub mod otp; pub mod pwm; mod reset; pub mod rom_data; diff --git a/embassy-rp/src/otp.rs b/embassy-rp/src/otp.rs new file mode 100644 index 000000000..cdaf5bded --- /dev/null +++ b/embassy-rp/src/otp.rs @@ -0,0 +1,108 @@ +//! Interface to the RP2350's One Time Programmable Memory + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// The ways in which we can fail to read OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read lock set earlier in the boot process. + InvalidPermissions, +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} + +/// Get the random 64bit chipid from rows 0x0-0x3. +pub fn get_chipid() -> Result { + let w0 = read_ecc_word(0x000)?.to_be_bytes(); + let w1 = read_ecc_word(0x001)?.to_be_bytes(); + let w2 = read_ecc_word(0x002)?.to_be_bytes(); + let w3 = read_ecc_word(0x003)?.to_be_bytes(); + + Ok(u64::from_be_bytes([ + w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} + +/// Get the 128bit private random number from rows 0x4-0xb. +/// +/// This ID is not exposed through the USB PICOBOOT GET_INFO command +/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP +/// access point can read the entirety of page 0, so this value is not +/// meaningfully private unless the USB PICOBOOT interface is disabled via the +//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0 +pub fn get_private_random_number() -> Result { + let w0 = read_ecc_word(0x004)?.to_be_bytes(); + let w1 = read_ecc_word(0x005)?.to_be_bytes(); + let w2 = read_ecc_word(0x006)?.to_be_bytes(); + let w3 = read_ecc_word(0x007)?.to_be_bytes(); + let w4 = read_ecc_word(0x008)?.to_be_bytes(); + let w5 = read_ecc_word(0x009)?.to_be_bytes(); + let w6 = read_ecc_word(0x00a)?.to_be_bytes(); + let w7 = read_ecc_word(0x00b)?.to_be_bytes(); + + Ok(u128::from_be_bytes([ + w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} diff --git a/examples/rp23/src/bin/otp.rs b/examples/rp23/src/bin/otp.rs new file mode 100644 index 000000000..c4d79c6ea --- /dev/null +++ b/examples/rp23/src/bin/otp.rs @@ -0,0 +1,46 @@ +//! This example shows reading the OTP constants on the RP235x. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::otp; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"OTP Read Example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"OTP Read Example"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _ = embassy_rp::init(Default::default()); + // + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let unique_id = unwrap!(otp::get_unique_id()); + info!("Unique id:{:X}", unique_id); + + let private_rand = unwrap!(otp::get_private_random_number()); + info!("Private Rand:{:X}", private_rand); + + loop { + Timer::after_secs(1).await; + } +} From 4c07c356e44020d39bc27d44d24272ad369426c3 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 29 Aug 2024 21:35:57 -0400 Subject: [PATCH 031/127] Fixup: forgot to rename fn in example --- examples/rp23/src/bin/otp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rp23/src/bin/otp.rs b/examples/rp23/src/bin/otp.rs index c4d79c6ea..106e514ca 100644 --- a/examples/rp23/src/bin/otp.rs +++ b/examples/rp23/src/bin/otp.rs @@ -34,8 +34,8 @@ async fn main(_spawner: Spawner) { // https://github.com/knurling-rs/defmt/pull/683 Timer::after_millis(10).await; - let unique_id = unwrap!(otp::get_unique_id()); - info!("Unique id:{:X}", unique_id); + let chip_id = unwrap!(otp::get_chipid()); + info!("Unique id:{:X}", chip_id); let private_rand = unwrap!(otp::get_private_random_number()); info!("Private Rand:{:X}", private_rand); From 82f274ea097c4aef35cb452d94654f94ab549895 Mon Sep 17 00:00:00 2001 From: Maxime Vincent Date: Mon, 2 Sep 2024 12:05:33 +0200 Subject: [PATCH 032/127] stm32: add f2 flash support (blocking) --- embassy-stm32/src/flash/f2.rs | 142 +++++++++++++++++++++++++++++++++ embassy-stm32/src/flash/mod.rs | 5 +- 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 embassy-stm32/src/flash/f2.rs diff --git a/embassy-stm32/src/flash/f2.rs b/embassy-stm32/src/flash/f2.rs new file mode 100644 index 000000000..cdab1fd2d --- /dev/null +++ b/embassy-stm32/src/flash/f2.rs @@ -0,0 +1,142 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; + +use pac::flash::regs::Sr; + +use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); + +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } +} + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); + restore_data_cache_state(); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + blocking_wait_ready() +} + +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Blocking erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()) + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + clear_all_err(); + restore_data_cache_state(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + return get_result(sr); + } + } +} + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.pgserr() { + Err(Error::Seq) + } else if sr.pgperr() { + Err(Error::Parallelism) + } else if sr.pgaerr() { + Err(Error::Unaligned) + } else if sr.wrperr() { + Err(Error::Protected) + } else { + Ok(()) + } +} + +fn save_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Disable data cache during write/erase if there are two banks, see errata 2.2.12 + let dcen = pac::FLASH.acr().read().dcen(); + DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed); + if dcen { + pac::FLASH.acr().modify(|w| w.set_dcen(false)); + } + } +} + +fn restore_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Restore data cache if it was enabled + let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed); + if dcen { + // Reset data cache before we enable it again + pac::FLASH.acr().modify(|w| w.set_dcrst(true)); + pac::FLASH.acr().modify(|w| w.set_dcrst(false)); + pac::FLASH.acr().modify(|w| w.set_dcen(true)) + } + } +} diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index ce2d1a04c..bce638db0 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -94,6 +94,7 @@ pub enum FlashBank { #[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")] #[cfg_attr(flash_f0, path = "f0.rs")] #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] +#[cfg_attr(flash_f2, path = "f2.rs")] #[cfg_attr(flash_f4, path = "f4.rs")] #[cfg_attr(flash_f7, path = "f7.rs")] #[cfg_attr(any(flash_g0, flash_g4c2, flash_g4c3, flash_g4c4), path = "g.rs")] @@ -104,8 +105,8 @@ pub enum FlashBank { #[cfg_attr(flash_u0, path = "u0.rs")] #[cfg_attr( not(any( - flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f3, flash_f4, flash_f7, flash_g0, - flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 + flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, flash_f7, + flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 )), path = "other.rs" )] From b277f42c9d681ce3c929858adffcefbdb7adabef Mon Sep 17 00:00:00 2001 From: Adrian Friedli Date: Mon, 2 Sep 2024 22:07:49 +0200 Subject: [PATCH 033/127] nrf52840: fix naming of LED states in examples (#3304) The LEDs on the nrf52840 DK are active low. --- examples/nrf52840/src/bin/channel.rs | 4 ++-- examples/nrf52840/src/bin/channel_sender_receiver.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/nrf52840/src/bin/channel.rs b/examples/nrf52840/src/bin/channel.rs index 7fcea9dbd..e06ba1c73 100644 --- a/examples/nrf52840/src/bin/channel.rs +++ b/examples/nrf52840/src/bin/channel.rs @@ -35,8 +35,8 @@ async fn main(spawner: Spawner) { loop { match CHANNEL.receive().await { - LedState::On => led.set_high(), - LedState::Off => led.set_low(), + LedState::On => led.set_low(), + LedState::Off => led.set_high(), } } } diff --git a/examples/nrf52840/src/bin/channel_sender_receiver.rs b/examples/nrf52840/src/bin/channel_sender_receiver.rs index 3095a04ec..29f70f91c 100644 --- a/examples/nrf52840/src/bin/channel_sender_receiver.rs +++ b/examples/nrf52840/src/bin/channel_sender_receiver.rs @@ -33,8 +33,8 @@ async fn recv_task(led: AnyPin, receiver: Receiver<'static, NoopRawMutex, LedSta loop { match receiver.receive().await { - LedState::On => led.set_high(), - LedState::Off => led.set_low(), + LedState::On => led.set_low(), + LedState::Off => led.set_high(), } } } From 32cff6530fdb81066451cc1d3f1fbbb420e985da Mon Sep 17 00:00:00 2001 From: Lucas Martins Mendes Date: Tue, 3 Sep 2024 16:02:39 -0300 Subject: [PATCH 034/127] chore: fix using default_features instead of default-features in embassy boot dependency (#3306) --- embassy-boot/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml index 85b3695a1..d27fe763e 100644 --- a/embassy-boot/Cargo.toml +++ b/embassy-boot/Cargo.toml @@ -27,7 +27,7 @@ features = ["defmt"] defmt = { version = "0.3", optional = true } digest = "0.10" log = { version = "0.4", optional = true } -ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } +ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true } embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } embedded-storage = "0.3.1" @@ -42,7 +42,7 @@ rand = "0.8" futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" critical-section = { version = "1.1.1", features = ["std"] } -ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } +ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] } [features] ed25519-dalek = ["dep:ed25519-dalek", "_verify"] From a6db8678eb5a7c9ebcf6449799b4ff525fe52d5f Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 11:04:36 +0200 Subject: [PATCH 035/127] Add utility for setting configuration for a context --- embassy-net-nrf91/src/at.rs | 45 ----- embassy-net-nrf91/src/context.rs | 196 +++++++++++++++++++ embassy-net-nrf91/src/lib.rs | 2 + examples/nrf9160/src/bin/modem_tcp_client.rs | 64 +++--- 4 files changed, 220 insertions(+), 87 deletions(-) delete mode 100644 embassy-net-nrf91/src/at.rs create mode 100644 embassy-net-nrf91/src/context.rs diff --git a/embassy-net-nrf91/src/at.rs b/embassy-net-nrf91/src/at.rs deleted file mode 100644 index 04cbf3876..000000000 --- a/embassy-net-nrf91/src/at.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::{Error, Control}; - -// Drives the control loop of the modem based on declarative configuration. -pub struct AtDriver<'a> { - control: Control<'a>, - config: Config, -} - -pub struct Config { - pub network: NetworkConfig, -} - -pub struct NetworkConfig { - pub apn: &'static str, - pub prot: AuthProtection, - pub userid: &'static str, - pub password: &'static str, -} - -#[repr(u8)] -pub enum AuthProtection { - None = 0, - Pap = 1, - Chap = 2, -} - -impl<'a> AtDriver<'a> { - pub async fn new(control: Control<'a>, config: Config) -> Result { - control.wait_init().await; - Ok(Self { - control, - config, - }) - } - - async fn setup(&self) -> Result<(), Error> { - - } - - pub fn run(&self, stack: Stack>) -> ! { - loop { - - } - } -} diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs new file mode 100644 index 000000000..6dda51793 --- /dev/null +++ b/embassy-net-nrf91/src/context.rs @@ -0,0 +1,196 @@ +use core::net::IpAddr; +use heapless::String; +use core::str::FromStr; +use core::fmt::Write; + +/// Provides a higher level API for configuring and reading information for a given +/// context id. +pub struct Control<'a> { + control: crate::Control<'a>, + cid: u8, +} + +pub struct Config<'a> { + pub gateway: &'a str, + pub auth_prot: AuthProt, + pub auth: Option<(&'a str, &'a str)>, +} + +#[repr(u8)] +pub enum AuthProt { + None = 0, + Pap = 1, + Chap = 2, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + BufferTooSmall, + AtCommand, + AddrParseError, + Format, +} + +impl From for Error { + fn from(_: core::fmt::Error) -> Self { + Self::Format + } +} + +#[derive(PartialEq, Debug)] +pub struct Status { + pub attached: bool, + pub ip: Option, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Status { + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!(f, "attached: {}", self.attached); + if let Some(ip) = &self.ip { + defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip)); + } + } +} + +impl<'a> Control<'a> { + pub async fn new(control: crate::Control<'a>, cid: u8) -> Self { + control.wait_init().await; + Self { control, cid } + } + + /// Bypass modem configurator + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + self.control.at_command(req, resp).await + } + + /// Configures the modem with the provided config. + pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> { + let mut cmd: String<128> = String::new(); + let mut buf: [u8; 256] = [0; 256]; + + write!(cmd, "AT+CGDCONT={},\"IP\",\"{}\"", self.cid, config.gateway).map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(cmd.as_bytes(), &mut buf).await; + let mut res = &buf[..n]; + let res = split_field(&mut res); + if res != b"OK" { + return Err(Error::AtCommand) + } + cmd.clear(); + + write!(cmd, "AT+CGAUTH={},{}", self.cid, config.auth_prot as u8)?; + if let Some((username, password)) = config.auth { + write!(cmd, ",\"{}\",\"{}\"", username, password).map_err(|_| Error::BufferTooSmall)?; + } + let n = self.control.at_command(cmd.as_bytes(), &mut buf).await; + let mut res = &buf[..n]; + let res = split_field(&mut res); + if res != b"OK" { + return Err(Error::AtCommand) + } + cmd.clear(); + + let n = self.control.at_command(b"AT+CFUN=1", &mut buf).await; + let mut res = &buf[..n]; + let res = split_field(&mut res); + if res != b"OK" { + return Err(Error::AtCommand); + } + + Ok(()) + } + + pub async fn status(&self) -> Result { + let mut buf: [u8; 256] = [0; 256]; + let n = self.control.at_command(b"AT+CGATT?", &mut buf).await; + let mut res = &buf[..n]; + pop_prefix(&mut res, b"+CGATT: "); + let res = split_field(&mut res); + let attached = res == b"1"; + + if !attached { + return Ok(Status { attached, ip: None }) + } + + let mut s: String<128> = String::new(); + write!(s, "AT+CGPADDR={}", self.cid)?; + let n = self.control.at_command(s.as_bytes(), &mut buf).await; + let mut res = &buf[..n]; + s.clear(); + + write!(s, "+CGPADDR: {},", self.cid)?; + + info!("RES: {:?}", unsafe {core::str::from_utf8_unchecked(res)}); + if s.len() > res.len() { + let res = split_field(&mut res); + if res == b"OK" { + Ok(Status { attached, ip: None }) + } else { + Err(Error::AtCommand) + } + } else { + pop_prefix(&mut res, s.as_bytes()); + + let ip = split_field(&mut res); + if !ip.is_empty() { + let ip = IpAddr::from_str(unsafe { core::str::from_utf8_unchecked(ip) }).map_err(|_| Error::AddrParseError)?; + self.control.open_raw_socket().await; + Ok(Status { attached, ip: Some(ip) }) + } else { + Ok(Status { attached, ip: None }) + } + } + } +} + +pub(crate) fn is_whitespace(char: u8) -> bool { + match char { + b'\r' | b'\n' | b' ' => true, + _ => false, + } +} + +pub(crate) fn is_separator(char: u8) -> bool { + match char { + b',' | b'\r' | b'\n' | b' ' => true, + _ => false, + } +} + +pub(crate) fn split_field<'a>(data: &mut &'a [u8]) -> &'a [u8] { + while !data.is_empty() && is_whitespace(data[0]) { + *data = &data[1..]; + } + + if data.is_empty() { + return &[]; + } + + if data[0] == b'"' { + let data2 = &data[1..]; + let end = data2.iter().position(|&x| x == b'"').unwrap_or(data2.len()); + let field = &data2[..end]; + let mut rest = &data2[data2.len().min(end + 1)..]; + if rest.first() == Some(&b'\"') { + rest = &rest[1..]; + } + while !rest.is_empty() && is_separator(rest[0]) { + rest = &rest[1..]; + } + *data = rest; + field + } else { + let end = data.iter().position(|&x| is_separator(x)).unwrap_or(data.len()); + let field = &data[0..end]; + let rest = &data[data.len().min(end + 1)..]; + *data = rest; + field + } +} + +pub(crate) fn pop_prefix(data: &mut &[u8], prefix: &[u8]) { + assert!(data.len() >= prefix.len()); + assert!(&data[..prefix.len()] == prefix); + *data = &data[prefix.len()..]; +} diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index 70ad176da..beed7c65a 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -6,6 +6,8 @@ // must be first mod fmt; +pub mod context; + use core::cell::RefCell; use core::future::poll_fn; use core::marker::PhantomData; diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index b1dac18a1..817ad17c7 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -2,14 +2,15 @@ #![no_main] use core::mem::MaybeUninit; +use core::net::IpAddr; use core::ptr::addr_of_mut; use core::str::FromStr; use core::{slice, str}; -use defmt::{assert, *}; +use defmt::{assert, info, warn, unwrap}; use embassy_executor::Spawner; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; -use embassy_net_nrf91::{Runner, State}; +use embassy_net_nrf91::{Runner, State, context}; use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; use embassy_nrf::uarte::Baudrate; @@ -63,9 +64,9 @@ async fn blink_task(pin: AnyPin) { let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); loop { led.set_high(); - Timer::after_millis(100).await; + Timer::after_millis(1000).await; led.set_low(); - Timer::after_millis(100).await; + Timer::after_millis(1000).await; } } @@ -123,51 +124,30 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(net_task(stack))); - control.wait_init().await; - info!("INIT OK"); + let control = context::Control::new(control, 0).await; - let mut buf = [0u8; 256]; - - let n = control.at_command(b"AT+CFUN?", &mut buf).await; - info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); - - let n = control - .at_command(b"AT+CGDCONT=0,\"IP\",\"iot.nat.es\"", &mut buf) - .await; - info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); - let n = control - .at_command(b"AT+CGAUTH=0,1,\"orange\",\"orange\"", &mut buf) - .await; - info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); - - let n = control.at_command(b"AT+CFUN=1", &mut buf).await; - info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); + unwrap!(control.configure(context::Config { + gateway: "iot.nat.es", + auth_prot: context::AuthProt::Pap, + auth: Some(("orange", "orange")), + }).await); info!("waiting for attach..."); - loop { - Timer::after_millis(500).await; - let n = control.at_command(b"AT+CGATT?", &mut buf).await; - let mut res = &buf[..n]; - pop_prefix(&mut res, b"+CGATT: "); - let res = split_field(&mut res); - info!("AT resp field: '{}'", unsafe { str::from_utf8_unchecked(res) }); - if res == b"1" { - break; - } + + let mut status = unwrap!(control.status().await); + while !status.attached && status.ip.is_none() { + Timer::after_millis(1000).await; + status = unwrap!(control.status().await); + info!("STATUS: {:?}", status); } - let n = control.at_command(b"AT+CGPADDR=0", &mut buf).await; - let mut res = &buf[..n]; - pop_prefix(&mut res, b"+CGPADDR: 0,"); - let ip = split_field(&mut res); - let ip = Ipv4Address::from_str(unsafe { str::from_utf8_unchecked(ip) }).unwrap(); - info!("IP: '{}'", ip); - - info!("============== OPENING SOCKET"); - control.open_raw_socket().await; + let Some(IpAddr::V4(addr)) = status.ip else { + panic!("Unexpected IP address"); + }; + let addr = Ipv4Address(addr.octets()); stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(ip, 32), + address: Ipv4Cidr::new(addr, 32), gateway: None, dns_servers: Default::default(), })); From aabdd45424ae71550be542a3a62872b33a4e0717 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 11:09:27 +0200 Subject: [PATCH 036/127] remove debug logging --- embassy-net-nrf91/src/context.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 6dda51793..c47b1ade6 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -121,7 +121,6 @@ impl<'a> Control<'a> { write!(s, "+CGPADDR: {},", self.cid)?; - info!("RES: {:?}", unsafe {core::str::from_utf8_unchecked(res)}); if s.len() > res.len() { let res = split_field(&mut res); if res == b"OK" { From b76b7ca9f5d64e83f7f69a6f7d97ba605ab2af7f Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 12:58:33 +0200 Subject: [PATCH 037/127] Use at-commands crate and support DNS * Use at-commands for building and parsing AT commands which has better error handling. * Retrieve DNS servers * Retrieve gateway * Update example to configure embassy-net with retrieved parameters. --- embassy-net-nrf91/Cargo.toml | 1 + embassy-net-nrf91/src/context.rs | 202 ++++++++++--------- examples/nrf9160/Cargo.toml | 1 + examples/nrf9160/src/bin/modem_tcp_client.rs | 78 ++----- 4 files changed, 126 insertions(+), 156 deletions(-) diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml index e2d596d59..07a0c8886 100644 --- a/embassy-net-nrf91/Cargo.toml +++ b/embassy-net-nrf91/Cargo.toml @@ -26,6 +26,7 @@ embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver- heapless = "0.8" embedded-io = "0.6.1" +at-commands = "0.5.4" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index c47b1ade6..b532dca14 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -2,6 +2,8 @@ use core::net::IpAddr; use heapless::String; use core::str::FromStr; use core::fmt::Write; +use heapless::Vec; +use at_commands::{builder::CommandBuilder, parser::CommandParser}; /// Provides a higher level API for configuring and reading information for a given /// context id. @@ -28,10 +30,17 @@ pub enum AuthProt { pub enum Error { BufferTooSmall, AtCommand, + AtParseError, AddrParseError, Format, } +impl From for Error { + fn from(_: at_commands::parser::ParseError) -> Self { + Self::AtParseError + } +} + impl From for Error { fn from(_: core::fmt::Error) -> Self { Self::Format @@ -42,6 +51,8 @@ impl From for Error { pub struct Status { pub attached: bool, pub ip: Option, + pub gateway: Option, + pub dns: Vec, } #[cfg(feature = "defmt")] @@ -67,129 +78,122 @@ impl<'a> Control<'a> { /// Configures the modem with the provided config. pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> { - let mut cmd: String<128> = String::new(); + let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; - write!(cmd, "AT+CGDCONT={},\"IP\",\"{}\"", self.cid, config.gateway).map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(cmd.as_bytes(), &mut buf).await; - let mut res = &buf[..n]; - let res = split_field(&mut res); - if res != b"OK" { - return Err(Error::AtCommand) - } - cmd.clear(); + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGDCONT") + .with_int_parameter(self.cid) + .with_string_parameter("IP") + .with_string_parameter(config.gateway) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; - write!(cmd, "AT+CGAUTH={},{}", self.cid, config.auth_prot as u8)?; + let mut op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGAUTH") + .with_int_parameter(self.cid) + .with_int_parameter(config.auth_prot as u8); if let Some((username, password)) = config.auth { - write!(cmd, ",\"{}\",\"{}\"", username, password).map_err(|_| Error::BufferTooSmall)?; + op = op.with_string_parameter(username).with_string_parameter(password); } - let n = self.control.at_command(cmd.as_bytes(), &mut buf).await; - let mut res = &buf[..n]; - let res = split_field(&mut res); - if res != b"OK" { - return Err(Error::AtCommand) - } - cmd.clear(); + let op = op.finish().map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(b"AT+CFUN=1", &mut buf).await; - let mut res = &buf[..n]; - let res = split_field(&mut res); - if res != b"OK" { - return Err(Error::AtCommand); - } + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(1) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; Ok(()) } pub async fn status(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; - let n = self.control.at_command(b"AT+CGATT?", &mut buf).await; - let mut res = &buf[..n]; - pop_prefix(&mut res, b"+CGATT: "); - let res = split_field(&mut res); - let attached = res == b"1"; + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res, ) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK").finish()?; + let attached = res == 1; if !attached { - return Ok(Status { attached, ip: None }) + return Ok(Status { attached, ip: None, gateway: None, dns: Vec::new() }) } - let mut s: String<128> = String::new(); - write!(s, "AT+CGPADDR={}", self.cid)?; - let n = self.control.at_command(s.as_bytes(), &mut buf).await; - let mut res = &buf[..n]; - s.clear(); + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGPADDR") + .with_int_parameter(self.cid) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_, ip1, ip2, ) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGPADDR: ") + .expect_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_identifier(b"\r\nOK").finish()?; - write!(s, "+CGPADDR: {},", self.cid)?; + let ip = if let Some(ip) = ip1 { + let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; + self.control.open_raw_socket().await; + Some(ip) + } else { + None + }; - if s.len() > res.len() { - let res = split_field(&mut res); - if res == b"OK" { - Ok(Status { attached, ip: None }) + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGCONTRDP") + .with_int_parameter(self.cid) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, mtu) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGCONTRDP: ") + .expect_int_parameter() + .expect_optional_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_identifier(b"\r\nOK").finish()?; + + let gateway = if let Some(ip) = gateway { + if ip.is_empty() { + None } else { - Err(Error::AtCommand) + Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) } } else { - pop_prefix(&mut res, s.as_bytes()); + None + }; - let ip = split_field(&mut res); - if !ip.is_empty() { - let ip = IpAddr::from_str(unsafe { core::str::from_utf8_unchecked(ip) }).map_err(|_| Error::AddrParseError)?; - self.control.open_raw_socket().await; - Ok(Status { attached, ip: Some(ip) }) - } else { - Ok(Status { attached, ip: None }) - } + let mut dns = Vec::new(); + if let Some(ip) = dns1 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap(); } - } -} -pub(crate) fn is_whitespace(char: u8) -> bool { - match char { - b'\r' | b'\n' | b' ' => true, - _ => false, - } -} - -pub(crate) fn is_separator(char: u8) -> bool { - match char { - b',' | b'\r' | b'\n' | b' ' => true, - _ => false, - } -} - -pub(crate) fn split_field<'a>(data: &mut &'a [u8]) -> &'a [u8] { - while !data.is_empty() && is_whitespace(data[0]) { - *data = &data[1..]; - } - - if data.is_empty() { - return &[]; - } - - if data[0] == b'"' { - let data2 = &data[1..]; - let end = data2.iter().position(|&x| x == b'"').unwrap_or(data2.len()); - let field = &data2[..end]; - let mut rest = &data2[data2.len().min(end + 1)..]; - if rest.first() == Some(&b'\"') { - rest = &rest[1..]; + if let Some(ip) = dns2 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap(); } - while !rest.is_empty() && is_separator(rest[0]) { - rest = &rest[1..]; - } - *data = rest; - field - } else { - let end = data.iter().position(|&x| is_separator(x)).unwrap_or(data.len()); - let field = &data[0..end]; - let rest = &data[data.len().min(end + 1)..]; - *data = rest; - field + + Ok(Status { + attached, + ip, + gateway, + dns, + }) } } - -pub(crate) fn pop_prefix(data: &mut &[u8], prefix: &[u8]) { - assert!(data.len() >= prefix.len()); - assert!(&data[..prefix.len()] == prefix); - *data = &data[prefix.len()..]; -} diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index fc24e99d2..9aeb99317 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml @@ -14,6 +14,7 @@ embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defm defmt = "0.3" defmt-rtt = "0.4" +heapless = "0.8" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 817ad17c7..f80861693 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -5,9 +5,10 @@ use core::mem::MaybeUninit; use core::net::IpAddr; use core::ptr::addr_of_mut; use core::str::FromStr; -use core::{slice, str}; +use core::slice; use defmt::{assert, info, warn, unwrap}; +use heapless::Vec; use embassy_executor::Spawner; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; use embassy_net_nrf91::{Runner, State, context}; @@ -146,10 +147,23 @@ async fn main(spawner: Spawner) { }; let addr = Ipv4Address(addr.octets()); + let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { + Some(Ipv4Address(addr.octets())) + } else { + None + }; + + let mut dns_servers = Vec::new(); + for dns in status.dns { + if let IpAddr::V4(ip) = dns { + unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); + } + } + stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { address: Ipv4Cidr::new(addr, 32), - gateway: None, - dns_servers: Default::default(), + gateway, + dns_servers, })); let mut rx_buffer = [0; 4096]; @@ -159,15 +173,16 @@ async fn main(spawner: Spawner) { socket.set_timeout(Some(Duration::from_secs(10))); info!("Connecting..."); - let host_addr = embassy_net::Ipv4Address::from_str("83.51.182.206").unwrap(); - if let Err(e) = socket.connect((host_addr, 8000)).await { + let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap(); + if let Err(e) = socket.connect((host_addr, 4242)).await { warn!("connect error: {:?}", e); + Timer::after_secs(1).await; continue; } info!("Connected to {:?}", socket.remote_endpoint()); let msg = b"Hello world!\n"; - loop { + for _ in 0..10 { if let Err(e) = socket.write_all(msg).await { warn!("write error: {:?}", e); break; @@ -177,54 +192,3 @@ async fn main(spawner: Spawner) { } } } - -fn is_whitespace(char: u8) -> bool { - match char { - b'\r' | b'\n' | b' ' => true, - _ => false, - } -} - -fn is_separator(char: u8) -> bool { - match char { - b',' | b'\r' | b'\n' | b' ' => true, - _ => false, - } -} - -fn split_field<'a>(data: &mut &'a [u8]) -> &'a [u8] { - while !data.is_empty() && is_whitespace(data[0]) { - *data = &data[1..]; - } - - if data.is_empty() { - return &[]; - } - - if data[0] == b'"' { - let data2 = &data[1..]; - let end = data2.iter().position(|&x| x == b'"').unwrap_or(data2.len()); - let field = &data2[..end]; - let mut rest = &data2[data2.len().min(end + 1)..]; - if rest.first() == Some(&b'\"') { - rest = &rest[1..]; - } - while !rest.is_empty() && is_separator(rest[0]) { - rest = &rest[1..]; - } - *data = rest; - field - } else { - let end = data.iter().position(|&x| is_separator(x)).unwrap_or(data.len()); - let field = &data[0..end]; - let rest = &data[data.len().min(end + 1)..]; - *data = rest; - field - } -} - -fn pop_prefix(data: &mut &[u8], prefix: &[u8]) { - assert!(data.len() >= prefix.len()); - assert!(&data[..prefix.len()] == prefix); - *data = &data[prefix.len()..]; -} From 5e27a3e64f46340e50bc6f61e6ef5a89e9f077ab Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 13:09:27 +0200 Subject: [PATCH 038/127] Document public API and fix warnings --- embassy-net-nrf91/src/context.rs | 48 +++++++++++--------- examples/nrf9160/src/bin/modem_tcp_client.rs | 6 +-- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index b532dca14..9c67cbc9f 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -1,38 +1,46 @@ +//! Helper utility to configure a specific modem context. use core::net::IpAddr; -use heapless::String; use core::str::FromStr; -use core::fmt::Write; use heapless::Vec; use at_commands::{builder::CommandBuilder, parser::CommandParser}; -/// Provides a higher level API for configuring and reading information for a given -/// context id. +/// Provides a higher level API for controlling a given context. pub struct Control<'a> { control: crate::Control<'a>, cid: u8, } +/// Configuration for a given context pub struct Config<'a> { - pub gateway: &'a str, + /// Desired APN address. + pub apn: &'a str, + /// Desired authentication protocol. pub auth_prot: AuthProt, + /// Credentials. pub auth: Option<(&'a str, &'a str)>, } +/// Authentication protocol. #[repr(u8)] pub enum AuthProt { + /// No authentication. None = 0, + /// PAP authentication. Pap = 1, + /// CHAP authentication. Chap = 2, } +/// Error returned by control. #[derive(Clone, Copy, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { + /// Not enough space for command. BufferTooSmall, - AtCommand, + /// Error parsing response from modem. AtParseError, + /// Error parsing IP addresses. AddrParseError, - Format, } impl From for Error { @@ -41,17 +49,16 @@ impl From for Error { } } -impl From for Error { - fn from(_: core::fmt::Error) -> Self { - Self::Format - } -} - +/// Status of a given context. #[derive(PartialEq, Debug)] pub struct Status { + /// Attached to APN or not. pub attached: bool, + /// IP if assigned. pub ip: Option, + /// Gateway if assigned. pub gateway: Option, + /// DNS servers if assigned. pub dns: Vec, } @@ -66,16 +73,14 @@ impl defmt::Format for Status { } impl<'a> Control<'a> { + /// Create a new instance of a control handle for a given context. + /// + /// Will wait for the modem to be initialized if not. pub async fn new(control: crate::Control<'a>, cid: u8) -> Self { control.wait_init().await; Self { control, cid } } - /// Bypass modem configurator - pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { - self.control.at_command(req, resp).await - } - /// Configures the modem with the provided config. pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; @@ -85,7 +90,7 @@ impl<'a> Control<'a> { .named("+CGDCONT") .with_int_parameter(self.cid) .with_string_parameter("IP") - .with_string_parameter(config.gateway) + .with_string_parameter(config.apn) .finish().map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; @@ -112,6 +117,7 @@ impl<'a> Control<'a> { Ok(()) } + /// Read current connectivity status for modem. pub async fn status(&self) -> Result { let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; @@ -134,7 +140,7 @@ impl<'a> Control<'a> { .with_int_parameter(self.cid) .finish().map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; - let (_, ip1, ip2, ) = CommandParser::parse(&buf[..n]) + let (_, ip1, _ip2, ) = CommandParser::parse(&buf[..n]) .expect_identifier(b"+CGPADDR: ") .expect_int_parameter() .expect_optional_string_parameter() @@ -154,7 +160,7 @@ impl<'a> Control<'a> { .with_int_parameter(self.cid) .finish().map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; - let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, mtu) = CommandParser::parse(&buf[..n]) + let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n]) .expect_identifier(b"+CGCONTRDP: ") .expect_int_parameter() .expect_optional_int_parameter() diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index f80861693..55ab2a707 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -7,7 +7,7 @@ use core::ptr::addr_of_mut; use core::str::FromStr; use core::slice; -use defmt::{assert, info, warn, unwrap}; +use defmt::{info, warn, unwrap}; use heapless::Vec; use embassy_executor::Spawner; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; @@ -93,7 +93,7 @@ async fn main(spawner: Spawner) { static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; let mut config = uarte::Config::default(); - config.baudrate = Baudrate::BAUD1M; + config.baudrate = Baudrate::BAUD115200; let trace_writer = TraceWriter(BufferedUarteTx::new( //let trace_uart = BufferedUarteTx::new( unsafe { peripherals::SERIAL0::steal() }, @@ -128,7 +128,7 @@ async fn main(spawner: Spawner) { let control = context::Control::new(control, 0).await; unwrap!(control.configure(context::Config { - gateway: "iot.nat.es", + apn: "iot.nat.es", auth_prot: context::AuthProt::Pap, auth: Some(("orange", "orange")), }).await); From b4221d75b87664485977d37df28f7319143411fc Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 14:09:17 +0200 Subject: [PATCH 039/127] Make tracing optional and use dedicated task --- embassy-net-nrf91/src/lib.rs | 90 +++++++++++++++----- examples/nrf9160/src/bin/modem_tcp_client.rs | 58 ++++++------- 2 files changed, 98 insertions(+), 50 deletions(-) diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index beed7c65a..673784cb2 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -19,12 +19,15 @@ use core::task::{Poll, Waker}; use embassy_net_driver_channel as ch; use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use heapless::Vec; use nrf9160_pac as pac; use pac::NVIC; +use embassy_sync::pipe; const RX_SIZE: usize = 8 * 1024; const TRACE_SIZE: usize = 16 * 1024; +const TRACE_BUF: usize = 1024; const MTU: usize = 1500; /// Network driver. @@ -91,11 +94,30 @@ impl<'a> Allocator<'a> { } /// Create a new nRF91 embassy-net driver. -pub async fn new<'a, TW: embedded_io::Write>( +pub async fn new<'a>( state: &'a mut State, shmem: &'a mut [MaybeUninit], - trace_writer: TW, -) -> (NetDriver<'a>, Control<'a>, Runner<'a, TW>) { +) -> (NetDriver<'a>, Control<'a>, Runner<'a>) { + let (n, c, r, _) = new_internal(state, shmem, None).await; + (n, c, r) +} + +/// Create a new nRF91 embassy-net driver with trace. +pub async fn new_with_trace<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: &'a mut TraceBuffer, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, TraceReader<'a>) { + let (n, c, r, t) = new_internal(state, shmem, Some(trace_buffer)).await; + (n, c, r, t.unwrap()) +} + +/// Create a new nRF91 embassy-net driver. +async fn new_internal<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: Option<&'a mut TraceBuffer>, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, Option>) { let shmem_len = shmem.len(); let shmem_ptr = shmem.as_mut_ptr() as *mut u8; @@ -205,21 +227,49 @@ pub async fn new<'a, TW: embedded_io::Write>( let state_ch = ch_runner.state_runner(); state_ch.set_link_state(ch::driver::LinkState::Up); + let (trace_reader, trace_writer) = if let Some(trace) = trace_buffer { + let (r, w) = trace.trace.split(); + (Some(r), Some(w)) + } else { + (None, None) + }; + let runner = Runner { ch: ch_runner, state: state_inner, trace_writer, }; - (device, control, runner) + (device, control, runner, trace_reader) } -/// Shared state for the drivver. +/// State holding modem traces. +pub struct TraceBuffer { + trace: pipe::Pipe, +} + +/// Represents writer half of the trace buffer. +pub type TraceWriter<'a> = pipe::Writer<'a, NoopRawMutex, TRACE_BUF>; + +/// Represents the reader half of the trace buffer. +pub type TraceReader<'a> = pipe::Reader<'a, NoopRawMutex, TRACE_BUF>; + +impl TraceBuffer { + /// Create a new TraceBuffer. + pub const fn new() -> Self { + Self { + trace: pipe::Pipe::new(), + } + } +} + +/// Shared state for the driver. pub struct State { ch: ch::State, inner: MaybeUninit>, } + impl State { /// Create a new State. pub const fn new() -> Self { @@ -272,7 +322,7 @@ struct StateInner { } impl StateInner { - fn poll(&mut self, trace_writer: &mut impl embedded_io::Write, ch: &mut ch::Runner) { + fn poll(&mut self, trace_writer: &mut Option>, ch: &mut ch::Runner) { trace!("poll!"); let ipc = unsafe { &*pac::IPC_NS::ptr() }; @@ -399,15 +449,17 @@ impl StateInner { }); } - fn handle_trace(writer: &mut impl embedded_io::Write, id: u8, data: &[u8]) { - trace!("trace: {} {}", id, data.len()); - let mut header = [0u8; 5]; - header[0] = 0xEF; - header[1] = 0xBE; - header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); - header[4] = id; - writer.write_all(&header).unwrap(); - writer.write_all(data).unwrap(); + fn handle_trace(writer: &mut Option>, id: u8, data: &[u8]) { + if let Some(writer) = writer { + trace!("trace: {} {}", id, data.len()); + let mut header = [0u8; 5]; + header[0] = 0xEF; + header[1] = 0xBE; + header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); + header[4] = id; + writer.try_write(&header).ok(); + writer.try_write(data).ok(); + } } fn process(&mut self, list: *mut List, is_control: bool, ch: &mut ch::Runner) -> bool { @@ -794,7 +846,7 @@ impl<'a> Control<'a> { /// Open the raw socket used for sending/receiving IP packets. /// /// This must be done after `AT+CFUN=1` (?) - pub async fn open_raw_socket(&self) { + async fn open_raw_socket(&self) { let mut msg: Message = unsafe { mem::zeroed() }; msg.channel = 2; // data msg.id = 0x7001_0004; // open socket @@ -822,13 +874,13 @@ impl<'a> Control<'a> { } /// Background runner for the driver. -pub struct Runner<'a, TW: embedded_io::Write> { +pub struct Runner<'a> { ch: ch::Runner<'a, MTU>, state: &'a RefCell, - trace_writer: TW, + trace_writer: Option>, } -impl<'a, TW: embedded_io::Write> Runner<'a, TW> { +impl<'a> Runner<'a> { /// Run the driver operation in the background. /// /// You must run this in a background task, concurrently with all network operations. diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 55ab2a707..c65e6e153 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -4,20 +4,20 @@ use core::mem::MaybeUninit; use core::net::IpAddr; use core::ptr::addr_of_mut; -use core::str::FromStr; use core::slice; +use core::str::FromStr; -use defmt::{info, warn, unwrap}; -use heapless::Vec; +use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; -use embassy_net_nrf91::{Runner, State, context}; +use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; use embassy_nrf::uarte::Baudrate; use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte}; use embassy_time::{Duration, Timer}; use embedded_io_async::Write; +use heapless::Vec; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -30,28 +30,17 @@ bind_interrupts!(struct Irqs { UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler; }); -// embassy-net-nrf91 only supports blocking trace write for now. -// We don't want to block packet processing with slow uart writes, so -// we make an adapter that writes whatever fits in the buffer and drops -// data if it's full. -struct TraceWriter(BufferedUarteTx<'static, peripherals::SERIAL0>); - -impl embedded_io::ErrorType for TraceWriter { - type Error = core::convert::Infallible; -} - -impl embedded_io::Write for TraceWriter { - fn write(&mut self, buf: &[u8]) -> Result { - let _ = self.0.try_write(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) +#[embassy_executor::task] +async fn trace_task(mut uart: BufferedUarteTx<'static, peripherals::SERIAL0>, reader: TraceReader<'static>) -> ! { + let mut rx = [0u8; 1024]; + loop { + let n = reader.read(&mut rx[..]).await; + unwrap!(uart.write_all(&rx[..n]).await); } } #[embassy_executor::task] -async fn modem_task(runner: Runner<'static, TraceWriter>) -> ! { +async fn modem_task(runner: Runner<'static>) -> ! { runner.run().await } @@ -93,8 +82,8 @@ async fn main(spawner: Spawner) { static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; let mut config = uarte::Config::default(); - config.baudrate = Baudrate::BAUD115200; - let trace_writer = TraceWriter(BufferedUarteTx::new( + config.baudrate = Baudrate::BAUD1M; + let uart = BufferedUarteTx::new( //let trace_uart = BufferedUarteTx::new( unsafe { peripherals::SERIAL0::steal() }, Irqs, @@ -102,11 +91,14 @@ async fn main(spawner: Spawner) { //unsafe { peripherals::P0_14::steal() }, config, unsafe { &mut *addr_of_mut!(TRACE_BUF) }, - )); + ); static STATE: StaticCell = StaticCell::new(); - let (device, control, runner) = embassy_net_nrf91::new(STATE.init(State::new()), ipc_mem, trace_writer).await; + static TRACE: StaticCell = StaticCell::new(); + let (device, control, runner, tracer) = + embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await; unwrap!(spawner.spawn(modem_task(runner))); + unwrap!(spawner.spawn(trace_task(uart, tracer))); let config = embassy_net::Config::default(); @@ -127,11 +119,15 @@ async fn main(spawner: Spawner) { let control = context::Control::new(control, 0).await; - unwrap!(control.configure(context::Config { - apn: "iot.nat.es", - auth_prot: context::AuthProt::Pap, - auth: Some(("orange", "orange")), - }).await); + unwrap!( + control + .configure(context::Config { + apn: "iot.nat.es", + auth_prot: context::AuthProt::Pap, + auth: Some(("orange", "orange")), + }) + .await + ); info!("waiting for attach..."); From 49881f6fd1e3d77d63dea2313afb5201eca8ebd9 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 14:21:13 +0200 Subject: [PATCH 040/127] rustfmt --- embassy-net-nrf91/src/context.rs | 45 ++++++++++++++++++++++---------- embassy-net-nrf91/src/lib.rs | 10 +++---- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 9c67cbc9f..954830417 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -1,8 +1,10 @@ //! Helper utility to configure a specific modem context. use core::net::IpAddr; use core::str::FromStr; + +use at_commands::builder::CommandBuilder; +use at_commands::parser::CommandParser; use heapless::Vec; -use at_commands::{builder::CommandBuilder, parser::CommandParser}; /// Provides a higher level API for controlling a given context. pub struct Control<'a> { @@ -91,7 +93,8 @@ impl<'a> Control<'a> { .with_int_parameter(self.cid) .with_string_parameter("IP") .with_string_parameter(config.apn) - .finish().map_err(|_| Error::BufferTooSmall)?; + .finish() + .map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; @@ -110,7 +113,8 @@ impl<'a> Control<'a> { let op = CommandBuilder::create_set(&mut cmd, true) .named("+CFUN") .with_int_parameter(1) - .finish().map_err(|_| Error::BufferTooSmall)?; + .finish() + .map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; @@ -124,28 +128,37 @@ impl<'a> Control<'a> { let op = CommandBuilder::create_query(&mut cmd, true) .named("+CGATT") - .finish().map_err(|_| Error::BufferTooSmall)?; + .finish() + .map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; - let (res, ) = CommandParser::parse(&buf[..n]) + let (res,) = CommandParser::parse(&buf[..n]) .expect_identifier(b"+CGATT: ") .expect_int_parameter() - .expect_identifier(b"\r\nOK").finish()?; + .expect_identifier(b"\r\nOK") + .finish()?; let attached = res == 1; if !attached { - return Ok(Status { attached, ip: None, gateway: None, dns: Vec::new() }) + return Ok(Status { + attached, + ip: None, + gateway: None, + dns: Vec::new(), + }); } let op = CommandBuilder::create_set(&mut cmd, true) .named("+CGPADDR") .with_int_parameter(self.cid) - .finish().map_err(|_| Error::BufferTooSmall)?; + .finish() + .map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; - let (_, ip1, _ip2, ) = CommandParser::parse(&buf[..n]) + let (_, ip1, _ip2) = CommandParser::parse(&buf[..n]) .expect_identifier(b"+CGPADDR: ") .expect_int_parameter() .expect_optional_string_parameter() .expect_optional_string_parameter() - .expect_identifier(b"\r\nOK").finish()?; + .expect_identifier(b"\r\nOK") + .finish()?; let ip = if let Some(ip) = ip1 { let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; @@ -158,7 +171,8 @@ impl<'a> Control<'a> { let op = CommandBuilder::create_set(&mut cmd, true) .named("+CGCONTRDP") .with_int_parameter(self.cid) - .finish().map_err(|_| Error::BufferTooSmall)?; + .finish() + .map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n]) .expect_identifier(b"+CGCONTRDP: ") @@ -174,7 +188,8 @@ impl<'a> Control<'a> { .expect_optional_int_parameter() .expect_optional_int_parameter() .expect_optional_int_parameter() - .expect_identifier(b"\r\nOK").finish()?; + .expect_identifier(b"\r\nOK") + .finish()?; let gateway = if let Some(ip) = gateway { if ip.is_empty() { @@ -188,11 +203,13 @@ impl<'a> Control<'a> { let mut dns = Vec::new(); if let Some(ip) = dns1 { - dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap(); + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); } if let Some(ip) = dns2 { - dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap(); + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); } Ok(Status { diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index 673784cb2..a60e27d97 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -17,13 +17,12 @@ use core::slice; use core::sync::atomic::{compiler_fence, fence, Ordering}; use core::task::{Poll, Waker}; -use embassy_net_driver_channel as ch; -use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use heapless::Vec; -use nrf9160_pac as pac; -use pac::NVIC; use embassy_sync::pipe; +use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; +use heapless::Vec; +use pac::NVIC; +use {embassy_net_driver_channel as ch, nrf9160_pac as pac}; const RX_SIZE: usize = 8 * 1024; const TRACE_SIZE: usize = 16 * 1024; @@ -269,7 +268,6 @@ pub struct State { inner: MaybeUninit>, } - impl State { /// Create a new State. pub const fn new() -> Self { From 372e45dabc0cfd3eb495e902665bb752a67aa804 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 18:47:26 +0200 Subject: [PATCH 041/127] Add context run task --- embassy-net-nrf91/src/context.rs | 86 +++++++++++++++++- embassy-net-nrf91/src/lib.rs | 5 +- examples/nrf9160/src/bin/modem_tcp_client.rs | 93 +++++++++++--------- 3 files changed, 137 insertions(+), 47 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 954830417..f73719224 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -5,6 +5,7 @@ use core::str::FromStr; use at_commands::builder::CommandBuilder; use at_commands::parser::CommandParser; use heapless::Vec; +use embassy_time::{Timer, Duration}; /// Provides a higher level API for controlling a given context. pub struct Control<'a> { @@ -23,6 +24,8 @@ pub struct Config<'a> { } /// Authentication protocol. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum AuthProt { /// No authentication. @@ -84,7 +87,7 @@ impl<'a> Control<'a> { } /// Configures the modem with the provided config. - pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> { + pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; @@ -118,9 +121,64 @@ impl<'a> Control<'a> { let n = self.control.at_command(op, &mut buf).await; CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("%XPDNCFG") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + + Ok(()) } + /// Attach to the PDN + pub async fn attach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Read current connectivity status for modem. + pub async fn detach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + async fn attached(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + Ok(res == 1) + } + /// Read current connectivity status for modem. pub async fn status(&self) -> Result { let mut cmd: [u8; 256] = [0; 256]; @@ -162,7 +220,6 @@ impl<'a> Control<'a> { let ip = if let Some(ip) = ip1 { let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; - self.control.open_raw_socket().await; Some(ip) } else { None @@ -219,4 +276,29 @@ impl<'a> Control<'a> { dns, }) } + + /// Run a control loop for this context, ensuring that reaattach is handled. + pub async fn run(&self, config: &Config<'_>, reattach: F) -> Result<(), Error> { + self.configure(config).await?; + while !self.attached().await? { + Timer::after(Duration::from_secs(1)).await; + } + let status = self.status().await?; + let mut fd = self.control.open_raw_socket().await; + reattach(&status); + + loop { + if !self.attached().await? { + // TODO: self.control.close_raw_socket(fd).await; + self.attach().await?; + while !self.attached().await? { + Timer::after(Duration::from_secs(1)).await; + } + let status = self.status().await?; + // TODO: let mut fd = self.control.open_raw_socket().await; + reattach(&status); + } + Timer::after(Duration::from_secs(10)).await; + } + } } diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index a60e27d97..ab3c6f327 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -844,7 +844,7 @@ impl<'a> Control<'a> { /// Open the raw socket used for sending/receiving IP packets. /// /// This must be done after `AT+CFUN=1` (?) - async fn open_raw_socket(&self) { + async fn open_raw_socket(&self) -> u32 { let mut msg: Message = unsafe { mem::zeroed() }; msg.channel = 2; // data msg.id = 0x7001_0004; // open socket @@ -867,7 +867,8 @@ impl<'a> Control<'a> { assert_eq!(status, 0); assert_eq!(msg.param_len, 16); let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap()); - debug!("got FD: {}", fd); + trace!("got FD: {}", fd); + fd } } diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index c65e6e153..a6f42eb3b 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -49,6 +49,43 @@ async fn net_task(stack: &'static Stack>) stack.run().await } +#[embassy_executor::task] +async fn control_task( + control: &'static context::Control<'static>, + config: context::Config<'static>, + stack: &'static Stack>, +) { + unwrap!( + control + .run(&config, |status| { + let Some(IpAddr::V4(addr)) = status.ip else { + panic!("Unexpected IP address"); + }; + let addr = Ipv4Address(addr.octets()); + + let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { + Some(Ipv4Address(addr.octets())) + } else { + None + }; + + let mut dns_servers = Vec::new(); + for dns in status.dns.iter() { + if let IpAddr::V4(ip) = dns { + unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); + } + } + + stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(addr, 32), + gateway, + dns_servers, + })); + }) + .await + ); +} + #[embassy_executor::task] async fn blink_task(pin: AnyPin) { let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); @@ -117,50 +154,20 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(net_task(stack))); - let control = context::Control::new(control, 0).await; + static CONTROL: StaticCell> = StaticCell::new(); + let control = CONTROL.init(context::Control::new(control, 0).await); - unwrap!( - control - .configure(context::Config { - apn: "iot.nat.es", - auth_prot: context::AuthProt::Pap, - auth: Some(("orange", "orange")), - }) - .await - ); + unwrap!(spawner.spawn(control_task( + control, + context::Config { + apn: "iot.nat.es", + auth_prot: context::AuthProt::Pap, + auth: Some(("orange", "orange")), + }, + stack + ))); - info!("waiting for attach..."); - - let mut status = unwrap!(control.status().await); - while !status.attached && status.ip.is_none() { - Timer::after_millis(1000).await; - status = unwrap!(control.status().await); - info!("STATUS: {:?}", status); - } - - let Some(IpAddr::V4(addr)) = status.ip else { - panic!("Unexpected IP address"); - }; - let addr = Ipv4Address(addr.octets()); - - let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { - Some(Ipv4Address(addr.octets())) - } else { - None - }; - - let mut dns_servers = Vec::new(); - for dns in status.dns { - if let IpAddr::V4(ip) = dns { - unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); - } - } - - stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(addr, 32), - gateway, - dns_servers, - })); + stack.wait_config_up().await; let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; @@ -172,7 +179,7 @@ async fn main(spawner: Spawner) { let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap(); if let Err(e) = socket.connect((host_addr, 4242)).await { warn!("connect error: {:?}", e); - Timer::after_secs(1).await; + Timer::after_secs(10).await; continue; } info!("Connected to {:?}", socket.remote_endpoint()); From aa3c7b900e5dfba440f86743cdf37c4e14ad60f0 Mon Sep 17 00:00:00 2001 From: Kezi Date: Wed, 4 Sep 2024 18:36:40 +0200 Subject: [PATCH 042/127] support custom style for usb logger --- embassy-usb-logger/src/lib.rs | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 34d1ca663..11188b4ef 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -41,12 +41,24 @@ pub const MAX_PACKET_SIZE: u8 = 64; /// The logger handle, which contains a pipe with configurable size for buffering log messages. pub struct UsbLogger { buffer: Pipe, + custom_style: Option) -> ()>, } impl UsbLogger { /// Create a new logger instance. pub const fn new() -> Self { - Self { buffer: Pipe::new() } + Self { + buffer: Pipe::new(), + custom_style: None, + } + } + + /// Create a new logger instance with a custom formatter. + pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self { + Self { + buffer: Pipe::new(), + custom_style: Some(custom_style), + } } /// Run the USB logger using the state and USB driver. Never returns. @@ -137,14 +149,19 @@ impl log::Log for UsbLogger { fn log(&self, record: &Record) { if self.enabled(record.metadata()) { - let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + if let Some(custom_style) = self.custom_style { + custom_style(record, &mut Writer(&self.buffer)); + } else { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } } } fn flush(&self) {} } -struct Writer<'d, const N: usize>(&'d Pipe); +/// A writer that writes to the USB logger buffer. +pub struct Writer<'d, const N: usize>(&'d Pipe); impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { @@ -210,3 +227,33 @@ macro_rules! with_class { LOGGER.create_future_from_class($p) }}; } + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// This version of the macro allows for a custom style function to be passed in. +/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer. +/// +/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. +/// +/// # Usage +/// +/// ``` +/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| { +/// use core::fmt::Write; +/// let level = record.level().as_str(); +/// write!(writer, "[{level}] {}\r\n", record.args()).unwrap(); +/// }); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_custom_style { + ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::with_custom_style($s); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; +} From ccfa6264b0ad258625f2dd667ba8e6eaca1cfdc3 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 19:31:55 +0200 Subject: [PATCH 043/127] Add closing if raw socket to handle re-attach --- embassy-net-nrf91/src/context.rs | 8 +++----- embassy-net-nrf91/src/lib.rs | 15 +++++++++++++++ examples/nrf9160/src/bin/modem_tcp_client.rs | 2 ++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index f73719224..511468316 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -4,8 +4,8 @@ use core::str::FromStr; use at_commands::builder::CommandBuilder; use at_commands::parser::CommandParser; +use embassy_time::{Duration, Timer}; use heapless::Vec; -use embassy_time::{Timer, Duration}; /// Provides a higher level API for controlling a given context. pub struct Control<'a> { @@ -129,8 +129,6 @@ impl<'a> Control<'a> { let n = self.control.at_command(op, &mut buf).await; CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; - - Ok(()) } @@ -289,13 +287,13 @@ impl<'a> Control<'a> { loop { if !self.attached().await? { - // TODO: self.control.close_raw_socket(fd).await; + self.control.close_raw_socket(fd).await; self.attach().await?; while !self.attached().await? { Timer::after(Duration::from_secs(1)).await; } let status = self.status().await?; - // TODO: let mut fd = self.control.open_raw_socket().await; + fd = self.control.open_raw_socket().await; reattach(&status); } Timer::after(Duration::from_secs(10)).await; diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index ab3c6f327..d8cbe47fc 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -870,6 +870,21 @@ impl<'a> Control<'a> { trace!("got FD: {}", fd); fd } + + async fn close_raw_socket(&self, fd: u32) { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7009_0004; // close socket + msg.param_len = 8; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80090004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + } } /// Background runner for the driver. diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index a6f42eb3b..fb14b746f 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -193,5 +193,7 @@ async fn main(spawner: Spawner) { info!("txd: {}", core::str::from_utf8(msg).unwrap()); Timer::after_secs(1).await; } + // Test auto-attach + unwrap!(control.detach().await); } } From e75903138a92e6c749454cee516c812df618bbfa Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 4 Sep 2024 13:42:03 -0400 Subject: [PATCH 044/127] Fix commented out code --- embassy-rp/src/flash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index dab99b4e2..fbc8b35ec 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -627,7 +627,7 @@ mod ram_helpers { #[link_section = ".data.ram_func"] #[cfg(feature = "rp2040")] unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { - //#[cfg(target_arch = "arm")] + #[cfg(target_arch = "arm")] core::arch::asm!( "mov r8, r0", "mov r9, r2", From df4ed5c91ded2e7292e72d7e38dc6c6e0958fece Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 4 Sep 2024 21:09:37 +0200 Subject: [PATCH 045/127] stm32/tests: disable flaky stm32wl55jc/usart_rx_ringbuffered --- ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.sh b/ci.sh index 70fe4f5d8..8fef731a4 100755 --- a/ci.sh +++ b/ci.sh @@ -305,6 +305,7 @@ rm out/tests/stm32u5a5zj/usart # flaky, probably due to bad ringbuffered dma code. rm out/tests/stm32l152re/usart_rx_ringbuffered rm out/tests/stm32f207zg/usart_rx_ringbuffered +rm out/tests/stm32wl55jc/usart_rx_ringbuffered if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests From 836e8add1bcb09c476a3aa3d7a416a15b8c21e6a Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 5 Sep 2024 10:02:45 +0200 Subject: [PATCH 046/127] Mintor fixes after testing re-attach --- embassy-net-nrf91/src/context.rs | 20 +++++--- examples/nrf9160/src/bin/modem_tcp_client.rs | 54 +++++++++++--------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 511468316..6b841aa16 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -275,24 +275,28 @@ impl<'a> Control<'a> { }) } - /// Run a control loop for this context, ensuring that reaattach is handled. - pub async fn run(&self, config: &Config<'_>, reattach: F) -> Result<(), Error> { - self.configure(config).await?; + async fn wait_attached(&self) -> Result { while !self.attached().await? { Timer::after(Duration::from_secs(1)).await; } let status = self.status().await?; + Ok(status) + } + + /// Run a control loop for this context, ensuring that reaattach is handled. + pub async fn run(&self, config: &Config<'_>, reattach: F) -> Result<(), Error> { + self.configure(config).await?; + let status = self.wait_attached().await?; let mut fd = self.control.open_raw_socket().await; reattach(&status); loop { if !self.attached().await? { + trace!("detached"); + self.control.close_raw_socket(fd).await; - self.attach().await?; - while !self.attached().await? { - Timer::after(Duration::from_secs(1)).await; - } - let status = self.status().await?; + let status = self.wait_attached().await?; + trace!("attached"); fd = self.control.open_raw_socket().await; reattach(&status); } diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index fb14b746f..b7d56802d 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -10,6 +10,7 @@ use core::str::FromStr; use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net_nrf91::context::Status; use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; @@ -58,34 +59,38 @@ async fn control_task( unwrap!( control .run(&config, |status| { - let Some(IpAddr::V4(addr)) = status.ip else { - panic!("Unexpected IP address"); - }; - let addr = Ipv4Address(addr.octets()); - - let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { - Some(Ipv4Address(addr.octets())) - } else { - None - }; - - let mut dns_servers = Vec::new(); - for dns in status.dns.iter() { - if let IpAddr::V4(ip) = dns { - unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); - } - } - - stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(addr, 32), - gateway, - dns_servers, - })); + stack.set_config_v4(status_to_config(status)); }) .await ); } +fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { + let Some(IpAddr::V4(addr)) = status.ip else { + panic!("Unexpected IP address"); + }; + let addr = Ipv4Address(addr.octets()); + + let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { + Some(Ipv4Address(addr.octets())) + } else { + None + }; + + let mut dns_servers = Vec::new(); + for dns in status.dns.iter() { + if let IpAddr::V4(ip) = dns { + unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); + } + } + + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(addr, 32), + gateway, + dns_servers, + }) +} + #[embassy_executor::task] async fn blink_task(pin: AnyPin) { let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); @@ -193,7 +198,6 @@ async fn main(spawner: Spawner) { info!("txd: {}", core::str::from_utf8(msg).unwrap()); Timer::after_secs(1).await; } - // Test auto-attach - unwrap!(control.detach().await); + Timer::after_secs(4).await; } } From 5d0ed246400b1e7973c1fe870dba977ab7186a21 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 5 Sep 2024 10:31:51 +0200 Subject: [PATCH 047/127] Move configure out of run --- embassy-net-nrf91/src/context.rs | 3 +-- examples/nrf9160/src/bin/modem_tcp_client.rs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 6b841aa16..3671fdd68 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -284,8 +284,7 @@ impl<'a> Control<'a> { } /// Run a control loop for this context, ensuring that reaattach is handled. - pub async fn run(&self, config: &Config<'_>, reattach: F) -> Result<(), Error> { - self.configure(config).await?; + pub async fn run(&self, reattach: F) -> Result<(), Error> { let status = self.wait_attached().await?; let mut fd = self.control.open_raw_socket().await; reattach(&status); diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index b7d56802d..7d78eba0a 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -56,9 +56,10 @@ async fn control_task( config: context::Config<'static>, stack: &'static Stack>, ) { + unwrap!(control.configure(&config).await); unwrap!( control - .run(&config, |status| { + .run(|status| { stack.set_config_v4(status_to_config(status)); }) .await From d71fd447cc4778be8abfdaf510be3a75dac23bf8 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 5 Sep 2024 11:25:19 +0200 Subject: [PATCH 048/127] Add method for buypassing and running at command directly --- embassy-net-nrf91/src/context.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 3671fdd68..b936e5f87 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -86,6 +86,11 @@ impl<'a> Control<'a> { Self { control, cid } } + /// Perform a raw AT command + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + self.control.at_command(req, resp).await + } + /// Configures the modem with the provided config. pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; From db00f3f5ecbf7f9a8acb340556b8fc6b156736fa Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Thu, 5 Sep 2024 11:45:49 +0200 Subject: [PATCH 049/127] Enable critical-section/std on wasm Without that feature one will find import errors on opening the webpage, that are hard to debug. The feature was indirectly enabled in the wasm example, however the reason wasn't documented and thus it was easy to miss. --- embassy-executor/Cargo.toml | 2 +- examples/wasm/Cargo.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 5984cc49c..01fa28b88 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -79,7 +79,7 @@ arch-cortex-m = ["_arch", "dep:cortex-m"] ## RISC-V 32 arch-riscv32 = ["_arch"] ## WASM -arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"] ## AVR arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 9bd37550c..75de079b7 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -16,7 +16,6 @@ wasm-logger = "0.2.0" wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] } log = "0.4.11" -critical-section = { version = "1.1", features = ["std"] } [profile.release] debug = 2 From ccf68d73910a68dc9e33beb666b20aba7430e015 Mon Sep 17 00:00:00 2001 From: elagil Date: Thu, 5 Sep 2024 21:29:04 +0200 Subject: [PATCH 050/127] feat(usb): add support for ISO endpoints --- embassy-stm32/src/usb/usb.rs | 211 ++++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 25 deletions(-) diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index 9384c8688..0ab2306c8 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -80,6 +80,8 @@ impl interrupt::typelevel::Handler for InterruptHandl if istr.ctr() { let index = istr.ep_id() as usize; + CTR_TRIGGERED[index].store(true, Ordering::Relaxed); + let mut epr = regs.epr(index).read(); if epr.ctr_rx() { if index == 0 && epr.setup() { @@ -120,6 +122,10 @@ const USBRAM_ALIGN: usize = 4; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; static EP0_SETUP: AtomicBool = AtomicBool::new(false); + +const NEW_CTR_TRIGGERED: AtomicBool = AtomicBool::new(false); +static CTR_TRIGGERED: [AtomicBool; EP_COUNT] = [NEW_CTR_TRIGGERED; EP_COUNT]; + static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; static IRQ_RESET: AtomicBool = AtomicBool::new(false); @@ -163,20 +169,37 @@ fn calc_out_len(len: u16) -> (u16, u16) { mod btable { use super::*; - pub(super) fn write_in(index: usize, addr: u16) { + pub(super) fn write_in_tx(index: usize, addr: u16) { USBRAM.mem(index * 4 + 0).write_value(addr); } - pub(super) fn write_in_len(index: usize, _addr: u16, len: u16) { + pub(super) fn write_in_rx(index: usize, addr: u16) { + USBRAM.mem(index * 4 + 2).write_value(addr); + } + + pub(super) fn write_in_len_rx(index: usize, _addr: u16, len: u16) { + USBRAM.mem(index * 4 + 3).write_value(len); + } + + pub(super) fn write_in_len_tx(index: usize, _addr: u16, len: u16) { USBRAM.mem(index * 4 + 1).write_value(len); } - pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { USBRAM.mem(index * 4 + 2).write_value(addr); USBRAM.mem(index * 4 + 3).write_value(max_len_bits); } - pub(super) fn read_out_len(index: usize) -> u16 { + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM.mem(index * 4 + 0).write_value(addr); + USBRAM.mem(index * 4 + 1).write_value(max_len_bits); + } + + pub(super) fn read_out_len_tx(index: usize) -> u16 { + USBRAM.mem(index * 4 + 1).read() + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { USBRAM.mem(index * 4 + 3).read() } } @@ -184,19 +207,37 @@ mod btable { mod btable { use super::*; - pub(super) fn write_in(_index: usize, _addr: u16) {} + pub(super) fn write_in_tx(_index: usize, _addr: u16) {} - pub(super) fn write_in_len(index: usize, addr: u16, len: u16) { + pub(super) fn write_in_rx(_index: usize, _addr: u16) {} + + pub(super) fn write_in_len_tx(index: usize, addr: u16, len: u16) { USBRAM.mem(index * 2).write_value((addr as u32) | ((len as u32) << 16)); } - pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + pub(super) fn write_in_len_rx(index: usize, addr: u16, len: u16) { + USBRAM + .mem(index * 2 + 1) + .write_value((addr as u32) | ((len as u32) << 16)); + } + + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM + .mem(index * 2) + .write_value((addr as u32) | ((max_len_bits as u32) << 16)); + } + + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { USBRAM .mem(index * 2 + 1) .write_value((addr as u32) | ((max_len_bits as u32) << 16)); } - pub(super) fn read_out_len(index: usize) -> u16 { + pub(super) fn read_out_len_tx(index: usize) -> u16 { + (USBRAM.mem(index * 2).read() >> 16) as u16 + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { (USBRAM.mem(index * 2 + 1).read() >> 16) as u16 } } @@ -327,6 +368,13 @@ impl<'d, T: Instance> Driver<'d, T> { return false; // reserved for control pipe } let used = ep.used_out || ep.used_in; + if used && (ep.ep_type == EndpointType::Isochronous || ep.ep_type == EndpointType::Bulk) { + // Isochronous and bulk endpoints are double-buffered. + // Their corresponding endpoint/channel registers are forced to be unidirectional. + // Do not reuse this index. + return false; + } + let used_dir = match D::dir() { Direction::Out => ep.used_out, Direction::In => ep.used_in, @@ -350,7 +398,11 @@ impl<'d, T: Instance> Driver<'d, T> { let addr = self.alloc_ep_mem(len); trace!(" len_bits = {:04x}", len_bits); - btable::write_out::(index, addr, len_bits); + btable::write_out_rx::(index, addr, len_bits); + + if ep_type == EndpointType::Isochronous { + btable::write_out_tx::(index, addr, len_bits); + } EndpointBuffer { addr, @@ -366,7 +418,11 @@ impl<'d, T: Instance> Driver<'d, T> { let addr = self.alloc_ep_mem(len); // ep_in_len is written when actually TXing packets. - btable::write_in::(index, addr); + btable::write_in_tx::(index, addr); + + if ep_type == EndpointType::Isochronous { + btable::write_in_rx::(index, addr); + } EndpointBuffer { addr, @@ -656,6 +712,18 @@ impl Dir for Out { } } +/// Selects the packet buffer. +/// +/// For double-buffered endpoints, both the `Rx` and `Tx` buffer from a channel are used for the same +/// direction of transfer. This is opposed to single-buffered endpoints, where one channel can serve +/// two directions at the same time. +enum PacketBuffer { + /// The RX buffer - must be used for single-buffered OUT endpoints + Rx, + /// The TX buffer - must be used for single-buffered IN endpoints + Tx, +} + /// USB endpoint. pub struct Endpoint<'d, T: Instance, D> { _phantom: PhantomData<(&'d mut T, D)>, @@ -664,15 +732,46 @@ pub struct Endpoint<'d, T: Instance, D> { } impl<'d, T: Instance, D> Endpoint<'d, T, D> { - fn write_data(&mut self, buf: &[u8]) { + /// Write to a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to write to the right counter field. + /// The DTOG_TX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next transmit packet will be stored, so we need to write the counter of the OTHER buffer, which is + /// where the last transmitted packet was stored. + fn write_data_double_buffered(&mut self, buf: &[u8], packet_buffer: PacketBuffer) { let index = self.info.addr.index(); self.buf.write(buf); - btable::write_in_len::(index, self.buf.addr, buf.len() as _); + + match packet_buffer { + PacketBuffer::Rx => btable::write_in_len_rx::(index, self.buf.addr, buf.len() as _), + PacketBuffer::Tx => btable::write_in_len_tx::(index, self.buf.addr, buf.len() as _), + } } - fn read_data(&mut self, buf: &mut [u8]) -> Result { + /// Write to a single-buffered endpoint. + fn write_data(&mut self, buf: &[u8]) { + self.write_data_double_buffered(buf, PacketBuffer::Tx); + } + + /// Read from a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to read from the right counter field. + /// The DTOG_RX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next received packet will be stored, so we need to read the counter of the OTHER buffer, which is + /// where the last received packet was stored. + fn read_data_double_buffered( + &mut self, + buf: &mut [u8], + packet_buffer: PacketBuffer, + ) -> Result { let index = self.info.addr.index(); - let rx_len = btable::read_out_len::(index) as usize & 0x3FF; + + let rx_len = match packet_buffer { + PacketBuffer::Rx => btable::read_out_len_rx::(index), + PacketBuffer::Tx => btable::read_out_len_tx::(index), + } as usize + & 0x3FF; + trace!("READ DONE, rx_len = {}", rx_len); if rx_len > buf.len() { return Err(EndpointError::BufferOverflow); @@ -680,6 +779,11 @@ impl<'d, T: Instance, D> Endpoint<'d, T, D> { self.buf.read(&mut buf[..rx_len]); Ok(rx_len) } + + /// Read from a single-buffered endpoint. + fn read_data(&mut self, buf: &mut [u8]) -> Result { + self.read_data_double_buffered(buf, PacketBuffer::Rx) + } } impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { @@ -734,25 +838,53 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { EP_OUT_WAKERS[index].register(cx.waker()); let regs = T::regs(); let stat = regs.epr(index).read().stat_rx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) { + Poll::Ready(stat) + } else { + Poll::Pending + } } else { - Poll::Pending + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } } }) .await; + CTR_TRIGGERED[index].store(false, Ordering::Relaxed); + if stat == Stat::DISABLED { return Err(EndpointError::Disabled); } - let rx_len = self.read_data(buf)?; - let regs = T::regs(); + + let packet_buffer = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Read from the OTHER buffer. + if regs.epr(index).read().dtog_rx() { + PacketBuffer::Rx + } else { + PacketBuffer::Tx + } + } else { + PacketBuffer::Rx + }; + + let rx_len = self.read_data_double_buffered(buf, packet_buffer)?; + regs.epr(index).write(|w| { w.set_ep_type(convert_type(self.info.ep_type)); w.set_ea(self.info.addr.index() as _); - w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + if self.info.ep_type == EndpointType::Isochronous { + w.set_stat_rx(Stat::from_bits(0)); // STAT_RX remains `VALID`. + } else { + w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + } w.set_stat_tx(Stat::from_bits(0)); w.set_ctr_rx(true); // don't clear w.set_ctr_tx(true); // don't clear @@ -776,25 +908,54 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { EP_IN_WAKERS[index].register(cx.waker()); let regs = T::regs(); let stat = regs.epr(index).read().stat_tx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) { + Poll::Ready(stat) + } else { + Poll::Pending + } } else { - Poll::Pending + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } } }) .await; + CTR_TRIGGERED[index].store(false, Ordering::Relaxed); + if stat == Stat::DISABLED { return Err(EndpointError::Disabled); } - self.write_data(buf); + let regs = T::regs(); + + let packet_buffer = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Write to the OTHER buffer. + if regs.epr(index).read().dtog_tx() { + PacketBuffer::Tx + } else { + PacketBuffer::Rx + } + } else { + PacketBuffer::Tx + }; + + self.write_data_double_buffered(buf, packet_buffer); let regs = T::regs(); regs.epr(index).write(|w| { w.set_ep_type(convert_type(self.info.ep_type)); w.set_ea(self.info.addr.index() as _); - w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + if self.info.ep_type == EndpointType::Isochronous { + w.set_stat_tx(Stat::from_bits(0)); // STAT_TX remains `VALID`. + } else { + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + } w.set_stat_rx(Stat::from_bits(0)); w.set_ctr_rx(true); // don't clear w.set_ctr_tx(true); // don't clear From d37c482e2179c95740bb4284f4df30637551199b Mon Sep 17 00:00:00 2001 From: elagil Date: Thu, 5 Sep 2024 21:29:11 +0200 Subject: [PATCH 051/127] feat(usb-otg): add support for ISO endpoints --- embassy-usb-synopsys-otg/src/lib.rs | 30 ++++++++++++++++++++++++++ embassy-usb-synopsys-otg/src/otg_v1.rs | 16 +++++++------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index 5fb82db42..b145f4aa8 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -1071,6 +1071,21 @@ impl<'d> embassy_usb_driver::EndpointOut for Endpoint<'d, Out> { w.set_pktcnt(1); }); + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.doepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + // Clear NAK to indicate we are ready to receive more data self.regs.doepctl(index).modify(|w| { w.set_cnak(true); @@ -1158,6 +1173,21 @@ impl<'d> embassy_usb_driver::EndpointIn for Endpoint<'d, In> { w.set_xfrsiz(buf.len() as _); }); + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.diepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + // Enable endpoint self.regs.diepctl(index).modify(|w| { w.set_cnak(true); diff --git a/embassy-usb-synopsys-otg/src/otg_v1.rs b/embassy-usb-synopsys-otg/src/otg_v1.rs index a8530980c..d3abc328d 100644 --- a/embassy-usb-synopsys-otg/src/otg_v1.rs +++ b/embassy-usb-synopsys-otg/src/otg_v1.rs @@ -795,15 +795,15 @@ pub mod regs { pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); } - #[doc = "SODDFRM/SD1PID"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub const fn soddfrm_sd1pid(&self) -> bool { + pub const fn sd1pid_soddfrm(&self) -> bool { let val = (self.0 >> 29usize) & 0x01; val != 0 } - #[doc = "SODDFRM/SD1PID"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub fn set_soddfrm_sd1pid(&mut self, val: bool) { + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); } #[doc = "EPDIS"] @@ -1174,15 +1174,15 @@ pub mod regs { pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); } - #[doc = "SODDFRM"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub const fn soddfrm(&self) -> bool { + pub const fn sd1pid_soddfrm(&self) -> bool { let val = (self.0 >> 29usize) & 0x01; val != 0 } - #[doc = "SODDFRM"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub fn set_soddfrm(&mut self, val: bool) { + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); } #[doc = "EPDIS"] From a8ca6713e6e9b7ad5dd53f9b46bcf5e893adda1e Mon Sep 17 00:00:00 2001 From: elagil Date: Thu, 5 Sep 2024 21:29:24 +0200 Subject: [PATCH 052/127] feat(usb): make use of ISO endpoint support --- embassy-usb/src/builder.rs | 140 ++++++++++++++++++++++++++++++---- embassy-usb/src/descriptor.rs | 91 +++++++++++++++++++--- 2 files changed, 206 insertions(+), 25 deletions(-) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 7168e077c..e1bf8041f 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,8 +1,8 @@ use heapless::Vec; use crate::config::MAX_HANDLER_COUNT; -use crate::descriptor::{BosWriter, DescriptorWriter}; -use crate::driver::{Driver, Endpoint, EndpointType}; +use crate::descriptor::{BosWriter, DescriptorWriter, SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointInfo, EndpointType}; use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::{InterfaceNumber, StringIndex}; use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; @@ -414,7 +414,7 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn descriptor(&mut self, descriptor_type: u8, descriptor: &[u8]) { - self.builder.config_descriptor.write(descriptor_type, descriptor); + self.builder.config_descriptor.write(descriptor_type, descriptor, &[]); } /// Add a custom Binary Object Store (BOS) descriptor to this alternate setting. @@ -422,26 +422,80 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.bos_descriptor.capability(capability_type, capability); } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + /// Write a custom endpoint descriptor for a certain endpoint. + /// + /// This can be necessary, if the endpoint descriptors can only be written + /// after the endpoint was created. As an example, an endpoint descriptor + /// may contain the address of an endpoint that was allocated earlier. + pub fn endpoint_descriptor( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + self.builder + .config_descriptor + .endpoint(endpoint, synchronization_type, usage_type, extra_fields); + } + + /// Allocate an IN endpoint, without writing its descriptor. + /// + /// Used for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { let ep = self .builder .driver .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); - self.builder.config_descriptor.endpoint(ep.info()); + ep + } + + fn endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + let ep = self.alloc_endpoint_in(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + /// Allocate an OUT endpoint, without writing its descriptor. + /// + /// Use for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> D::EndpointOut { let ep = self .builder .driver .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); - self.builder.config_descriptor.endpoint(ep.info()); + ep + } + + fn endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + let ep = self.alloc_endpoint_out(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); ep } @@ -451,7 +505,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_bulk_in(&mut self, max_packet_size: u16) -> D::EndpointIn { - self.endpoint_in(EndpointType::Bulk, max_packet_size, 0) + self.endpoint_in( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a BULK OUT endpoint and write its descriptor. @@ -459,7 +520,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_bulk_out(&mut self, max_packet_size: u16) -> D::EndpointOut { - self.endpoint_out(EndpointType::Bulk, max_packet_size, 0) + self.endpoint_out( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a INTERRUPT IN endpoint and write its descriptor. @@ -467,24 +535,66 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms) + self.endpoint_in( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms) + self.endpoint_out( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. /// /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. - pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms) + pub fn endpoint_isochronous_in( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) } /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. - pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms) + pub fn endpoint_isochronous_out( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) } } diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index f1773fa8a..0f2931c38 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,4 +1,5 @@ //! Utilities for writing USB descriptors. +use embassy_usb_driver::EndpointType; use crate::builder::Config; use crate::driver::EndpointInfo; @@ -38,6 +39,40 @@ pub mod capability_type { pub const PLATFORM: u8 = 5; } +/// USB endpoint synchronization type. The values of this enum can be directly +/// cast into `u8` to get the bmAttributes synchronization type bits. +/// Values other than `NoSynchronization` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SynchronizationType { + /// No synchronization is used. + NoSynchronization = 0b00, + /// Unsynchronized, although sinks provide data rate feedback. + Asynchronous = 0b01, + /// Synchronized using feedback or feedforward data rate information. + Adaptive = 0b10, + /// Synchronized to the USBā€™s SOF. + Synchronous = 0b11, +} + +/// USB endpoint usage type. The values of this enum can be directly cast into +/// `u8` to get the bmAttributes usage type bits. +/// Values other than `DataEndpoint` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsageType { + /// Use the endpoint for regular data transfer. + DataEndpoint = 0b00, + /// Endpoint conveys explicit feedback information for one or more data endpoints. + FeedbackEndpoint = 0b01, + /// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints. + ImplicitFeedbackDataEndpoint = 0b10, + /// Reserved usage type. + Reserved = 0b11, +} + /// A writer for USB descriptors. pub(crate) struct DescriptorWriter<'a> { pub buf: &'a mut [u8], @@ -65,23 +100,26 @@ impl<'a> DescriptorWriter<'a> { self.position } - /// Writes an arbitrary (usually class-specific) descriptor. - pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) { - let length = descriptor.len(); + /// Writes an arbitrary (usually class-specific) descriptor with optional extra fields. + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) { + let descriptor_length = descriptor.len(); + let extra_fields_length = extra_fields.len(); + let total_length = descriptor_length + extra_fields_length; assert!( - (self.position + 2 + length) <= self.buf.len() && (length + 2) <= 255, + (self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255, "Descriptor buffer full" ); - self.buf[self.position] = (length + 2) as u8; + self.buf[self.position] = (total_length + 2) as u8; self.buf[self.position + 1] = descriptor_type; let start = self.position + 2; - self.buf[start..start + length].copy_from_slice(descriptor); + self.buf[start..start + descriptor_length].copy_from_slice(descriptor); + self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields); - self.position = start + length; + self.position = start + total_length; } pub(crate) fn configuration(&mut self, config: &Config) { @@ -99,6 +137,7 @@ impl<'a> DescriptorWriter<'a> { | if config.supports_remote_wakeup { 0x20 } else { 0x00 }, // bmAttributes (config.max_power / 2) as u8, // bMaxPower ], + &[], ); } @@ -145,6 +184,7 @@ impl<'a> DescriptorWriter<'a> { function_protocol, 0, ], + &[], ); } @@ -195,6 +235,7 @@ impl<'a> DescriptorWriter<'a> { interface_protocol, // bInterfaceProtocol str_index, // iInterface ], + &[], ); } @@ -204,21 +245,50 @@ impl<'a> DescriptorWriter<'a> { /// /// * `endpoint` - Endpoint previously allocated with /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). - pub fn endpoint(&mut self, endpoint: &EndpointInfo) { + /// * `synchronization_type` - The synchronization type of the endpoint. + /// * `usage_type` - The usage type of the endpoint. + /// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor. + pub fn endpoint( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { match self.num_endpoints_mark { Some(mark) => self.buf[mark] += 1, None => panic!("you can only call `endpoint` after `interface/interface_alt`."), }; + let mut bm_attributes = endpoint.ep_type as u8; + + // Synchronization types other than `NoSynchronization`, + // and usage types other than `DataEndpoint` + // are only allowed for isochronous endpoints. + if endpoint.ep_type != EndpointType::Isochronous { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization); + assert_eq!(usage_type, UsageType::DataEndpoint); + } else { + if usage_type == UsageType::FeedbackEndpoint { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization) + } + + let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2; + let usage_bm_attibutes: u8 = (usage_type as u8) << 4; + + bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes; + } + self.write( descriptor_type::ENDPOINT, &[ - endpoint.addr.into(), // bEndpointAddress - endpoint.ep_type as u8, // bmAttributes + endpoint.addr.into(), // bEndpointAddress + bm_attributes, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize endpoint.interval_ms, // bInterval ], + extra_fields, ); } @@ -315,6 +385,7 @@ impl<'a> BosWriter<'a> { 0x00, 0x00, // wTotalLength 0x00, // bNumDeviceCaps ], + &[], ); self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]); From ccce870642f295cda901493df422b25d211da503 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 5 Sep 2024 18:01:05 -0400 Subject: [PATCH 053/127] ci: Fix the "test" task when using stm32-metapac from a CI artifact --- .github/ci/test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ci/test.sh b/.github/ci/test.sh index da0021684..0de265049 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -8,6 +8,10 @@ export RUSTUP_HOME=/ci/cache/rustup export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + cargo test --manifest-path ./embassy-futures/Cargo.toml cargo test --manifest-path ./embassy-sync/Cargo.toml cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml From ac7ebea762519505a0ea54ce753ec2a9fc67dfe9 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 6 Sep 2024 09:34:39 +0200 Subject: [PATCH 054/127] Make sure to CFUN=0 before changing configuration --- embassy-net-nrf91/src/context.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index b936e5f87..d5d088ec0 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -92,10 +92,21 @@ impl<'a> Control<'a> { } /// Configures the modem with the provided config. + /// + /// NOTE: This will disconnect the modem from any current APN and should not + /// be called if the configuration has not been changed. pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + let op = CommandBuilder::create_set(&mut cmd, true) .named("+CGDCONT") .with_int_parameter(self.cid) @@ -104,6 +115,7 @@ impl<'a> Control<'a> { .finish() .map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; + // info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; let mut op = CommandBuilder::create_set(&mut cmd, true) @@ -116,6 +128,7 @@ impl<'a> Control<'a> { let op = op.finish().map_err(|_| Error::BufferTooSmall)?; let n = self.control.at_command(op, &mut buf).await; + // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; let op = CommandBuilder::create_set(&mut cmd, true) From 5e74434cb8b89b62f7bc0d2a9ab90d94aa11fe4b Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 6 Sep 2024 09:52:26 +0200 Subject: [PATCH 055/127] Ensure modem is enabled in run() --- embassy-net-nrf91/src/context.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index d5d088ec0..2f2452bd0 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -303,6 +303,18 @@ impl<'a> Control<'a> { /// Run a control loop for this context, ensuring that reaattach is handled. pub async fn run(&self, reattach: F) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + // Make sure modem is enabled + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; let status = self.wait_attached().await?; let mut fd = self.control.open_raw_socket().await; reattach(&status); From e2e3143c2ea9a606eff2fa1d4e51a74824b5c2ac Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 6 Sep 2024 09:58:54 +0200 Subject: [PATCH 056/127] Add explicit disable/enable function and skip enable in configure --- embassy-net-nrf91/src/context.rs | 55 ++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 2f2452bd0..22629fe5b 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -95,6 +95,8 @@ impl<'a> Control<'a> { /// /// NOTE: This will disconnect the modem from any current APN and should not /// be called if the configuration has not been changed. + /// + /// After configuring, invoke [`enable()`] to activate the configuration. pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; @@ -131,22 +133,6 @@ impl<'a> Control<'a> { // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; - let op = CommandBuilder::create_set(&mut cmd, true) - .named("+CFUN") - .with_int_parameter(1) - .finish() - .map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(op, &mut buf).await; - CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; - - let op = CommandBuilder::create_set(&mut cmd, true) - .named("%XPDNCFG") - .with_int_parameter(1) - .finish() - .map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(op, &mut buf).await; - CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; - Ok(()) } @@ -301,20 +287,49 @@ impl<'a> Control<'a> { Ok(status) } - /// Run a control loop for this context, ensuring that reaattach is handled. - pub async fn run(&self, reattach: F) -> Result<(), Error> { + /// Disable modem + pub async fn disable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + Ok(()) + } + + /// Enable modem + pub async fn enable(&self) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; - // Make sure modem is enabled let op = CommandBuilder::create_set(&mut cmd, true) .named("+CFUN") .with_int_parameter(1) .finish() .map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(op, &mut buf).await; CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + // Make modem survive PDN detaches + let op = CommandBuilder::create_set(&mut cmd, true) + .named("%XPDNCFG") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Run a control loop for this context, ensuring that reaattach is handled. + pub async fn run(&self, reattach: F) -> Result<(), Error> { + self.enable().await?; let status = self.wait_attached().await?; let mut fd = self.control.open_raw_socket().await; reattach(&status); From 01d8508b6c9bf89e27a83b9587bcc13c358c1e51 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Fri, 6 Sep 2024 11:16:44 +0200 Subject: [PATCH 057/127] fix: nightly api changed during the night --- embassy-executor/src/raw/waker.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 8d3910a25..8bb2cfd05 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs @@ -50,8 +50,7 @@ pub fn task_from_waker(waker: &Waker) -> TaskRef { #[cfg(feature = "nightly")] { - let raw_waker = waker.as_raw(); - (raw_waker.vtable(), raw_waker.data()) + (waker.vtable(), waker.data()) } }; From 1b1db2401bfdfe6f813fb7738529749e4ec80882 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 6 Sep 2024 11:22:07 +0200 Subject: [PATCH 058/127] Use byte slice for config --- embassy-net-nrf91/src/context.rs | 4 ++-- examples/nrf9160/src/bin/modem_tcp_client.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index 22629fe5b..8b45919ef 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -16,11 +16,11 @@ pub struct Control<'a> { /// Configuration for a given context pub struct Config<'a> { /// Desired APN address. - pub apn: &'a str, + pub apn: &'a [u8], /// Desired authentication protocol. pub auth_prot: AuthProt, /// Credentials. - pub auth: Option<(&'a str, &'a str)>, + pub auth: Option<(&'a [u8], &'a [u8])>, } /// Authentication protocol. diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 7d78eba0a..5335b6b51 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -166,9 +166,9 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(control_task( control, context::Config { - apn: "iot.nat.es", + apn: b"iot.nat.es", auth_prot: context::AuthProt::Pap, - auth: Some(("orange", "orange")), + auth: Some((b"orange", b"orange")), }, stack ))); From 5b4941a51040d1bbcc00590a504e078e2f72f3bd Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Fri, 6 Sep 2024 11:29:12 +0200 Subject: [PATCH 059/127] ci: update rust-toolchain --- rust-toolchain-nightly.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain-nightly.toml b/rust-toolchain-nightly.toml index dfa231344..0b10d7194 100644 --- a/rust-toolchain-nightly.toml +++ b/rust-toolchain-nightly.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-07-16" +channel = "nightly-2024-09-06" components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] targets = [ "thumbv7em-none-eabi", From 1443f3386b3b216dc50306d14f5dacce29b2bf97 Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Fri, 6 Sep 2024 11:34:30 +0200 Subject: [PATCH 060/127] fix: remove stable nightly feature --- embassy-executor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 553ed76d3..6a2e493a2 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(waker_getters))] #![allow(clippy::new_without_default)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] From ee25f14b20fc2aabf428fbc7b2bd684ece66a2a1 Mon Sep 17 00:00:00 2001 From: Badr Bouslikhin Date: Fri, 6 Sep 2024 18:35:23 +0200 Subject: [PATCH 061/127] fix(stm32): reorder dma and idle futures --- embassy-stm32/src/usart/ringbuffered.rs | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 8cf75933a..b0652046c 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -184,20 +184,6 @@ impl<'d> RingBufferedUartRx<'d> { async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { compiler_fence(Ordering::SeqCst); - let mut dma_init = false; - // Future which completes when there is dma is half full or full - let dma = poll_fn(|cx| { - self.ring_buf.set_waker(cx.waker()); - - let status = match dma_init { - false => Poll::Pending, - true => Poll::Ready(()), - }; - - dma_init = true; - status - }); - // Future which completes when idle line is detected let s = self.state; let uart = poll_fn(|cx| { @@ -219,9 +205,23 @@ impl<'d> RingBufferedUartRx<'d> { } }); - match select(dma, uart).await { - Either::Left(((), _)) => Ok(()), - Either::Right((result, _)) => result, + let mut dma_init = false; + // Future which completes when there is dma is half full or full + let dma = poll_fn(|cx| { + self.ring_buf.set_waker(cx.waker()); + + let status = match dma_init { + false => Poll::Pending, + true => Poll::Ready(()), + }; + + dma_init = true; + status + }); + + match select(uart, dma).await { + Either::Left((result, _)) => result, + Either::Right(((), _)) => Ok(()), } } } From 0e477a4df506981e770e41edec31e2bc0cd6b6c6 Mon Sep 17 00:00:00 2001 From: Badr Bouslikhin Date: Fri, 6 Sep 2024 18:36:11 +0200 Subject: [PATCH 062/127] fix(stm32): enable dma half transfer interrupt for buffereduart --- embassy-stm32/src/dma/dma_bdma.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 8e2964f94..df041c4e9 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -777,6 +777,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { let dir = Dir::PeripheralToMemory; let data_size = W::size(); + options.half_transfer_ir = true; options.complete_transfer_ir = true; options.circular = true; From 74e724f96869680da4893ad7024676bef1c57334 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 9 Sep 2024 02:13:17 +0200 Subject: [PATCH 063/127] Update to Rust 1.81 (#3322) --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ce9040a70..80acb3b5e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.80" +channel = "1.81" components = [ "rust-src", "rustfmt", "llvm-tools" ] targets = [ "thumbv7em-none-eabi", From 3ac38e917cc56530fa9b3515fd28a1f576b285e8 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 8 Sep 2024 23:48:45 +0200 Subject: [PATCH 064/127] cyw43: use enum for ioctl instead of consts. --- cyw43/src/consts.rs | 299 +++++++++++++++++++++++++++++++++++++++++-- cyw43/src/control.rs | 59 ++++----- cyw43/src/ioctl.rs | 5 +- cyw43/src/runner.rs | 4 +- 4 files changed, 319 insertions(+), 48 deletions(-) diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs index b6e22e61d..d47e5097a 100644 --- a/cyw43/src/consts.rs +++ b/cyw43/src/consts.rs @@ -113,17 +113,6 @@ pub(crate) const IRQ_F1_INTR: u16 = 0x2000; pub(crate) const IRQ_F2_INTR: u16 = 0x4000; pub(crate) const IRQ_F3_INTR: u16 = 0x8000; -pub(crate) const IOCTL_CMD_UP: u32 = 2; -pub(crate) const IOCTL_CMD_DOWN: u32 = 3; -pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; -pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; -pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52; -pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; -pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; -pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; -pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; -pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; - pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; @@ -376,3 +365,291 @@ impl core::fmt::Display for FormatInterrupt { core::fmt::Debug::fmt(self, f) } } + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub(crate) enum Ioctl { + GetMagic = 0, + GetVersion = 1, + Up = 2, + Down = 3, + GetLoop = 4, + SetLoop = 5, + Dump = 6, + GetMsglevel = 7, + SetMsglevel = 8, + GetPromisc = 9, + SetPromisc = 10, + GetRate = 12, + GetInstance = 14, + GetInfra = 19, + SetInfra = 20, + GetAuth = 21, + SetAuth = 22, + GetBssid = 23, + SetBssid = 24, + GetSsid = 25, + SetSsid = 26, + Restart = 27, + GetChannel = 29, + SetChannel = 30, + GetSrl = 31, + SetSrl = 32, + GetLrl = 33, + SetLrl = 34, + GetPlcphdr = 35, + SetPlcphdr = 36, + GetRadio = 37, + SetRadio = 38, + GetPhytype = 39, + DumpRate = 40, + SetRateParams = 41, + GetKey = 44, + SetKey = 45, + GetRegulatory = 46, + SetRegulatory = 47, + GetPassiveScan = 48, + SetPassiveScan = 49, + Scan = 50, + ScanResults = 51, + Disassoc = 52, + Reassoc = 53, + GetRoamTrigger = 54, + SetRoamTrigger = 55, + GetRoamDelta = 56, + SetRoamDelta = 57, + GetRoamScanPeriod = 58, + SetRoamScanPeriod = 59, + Evm = 60, + GetTxant = 61, + SetTxant = 62, + GetAntdiv = 63, + SetAntdiv = 64, + GetClosed = 67, + SetClosed = 68, + GetMaclist = 69, + SetMaclist = 70, + GetRateset = 71, + SetRateset = 72, + Longtrain = 74, + GetBcnprd = 75, + SetBcnprd = 76, + GetDtimprd = 77, + SetDtimprd = 78, + GetSrom = 79, + SetSrom = 80, + GetWepRestrict = 81, + SetWepRestrict = 82, + GetCountry = 83, + SetCountry = 84, + GetPm = 85, + SetPm = 86, + GetWake = 87, + SetWake = 88, + GetForcelink = 90, + SetForcelink = 91, + FreqAccuracy = 92, + CarrierSuppress = 93, + GetPhyreg = 94, + SetPhyreg = 95, + GetRadioreg = 96, + SetRadioreg = 97, + GetRevinfo = 98, + GetUcantdiv = 99, + SetUcantdiv = 100, + RReg = 101, + WReg = 102, + GetMacmode = 105, + SetMacmode = 106, + GetMonitor = 107, + SetMonitor = 108, + GetGmode = 109, + SetGmode = 110, + GetLegacyErp = 111, + SetLegacyErp = 112, + GetRxAnt = 113, + GetCurrRateset = 114, + GetScansuppress = 115, + SetScansuppress = 116, + GetAp = 117, + SetAp = 118, + GetEapRestrict = 119, + SetEapRestrict = 120, + ScbAuthorize = 121, + ScbDeauthorize = 122, + GetWdslist = 123, + SetWdslist = 124, + GetAtim = 125, + SetAtim = 126, + GetRssi = 127, + GetPhyantdiv = 128, + SetPhyantdiv = 129, + ApRxOnly = 130, + GetTxPathPwr = 131, + SetTxPathPwr = 132, + GetWsec = 133, + SetWsec = 134, + GetPhyNoise = 135, + GetBssInfo = 136, + GetPktcnts = 137, + GetLazywds = 138, + SetLazywds = 139, + GetBandlist = 140, + GetBand = 141, + SetBand = 142, + ScbDeauthenticate = 143, + GetShortslot = 144, + GetShortslotOverride = 145, + SetShortslotOverride = 146, + GetShortslotRestrict = 147, + SetShortslotRestrict = 148, + GetGmodeProtection = 149, + GetGmodeProtectionOverride = 150, + SetGmodeProtectionOverride = 151, + Upgrade = 152, + GetIgnoreBcns = 155, + SetIgnoreBcns = 156, + GetScbTimeout = 157, + SetScbTimeout = 158, + GetAssoclist = 159, + GetClk = 160, + SetClk = 161, + GetUp = 162, + Out = 163, + GetWpaAuth = 164, + SetWpaAuth = 165, + GetUcflags = 166, + SetUcflags = 167, + GetPwridx = 168, + SetPwridx = 169, + GetTssi = 170, + GetSupRatesetOverride = 171, + SetSupRatesetOverride = 172, + GetProtectionControl = 178, + SetProtectionControl = 179, + GetPhylist = 180, + EncryptStrength = 181, + DecryptStatus = 182, + GetKeySeq = 183, + GetScanChannelTime = 184, + SetScanChannelTime = 185, + GetScanUnassocTime = 186, + SetScanUnassocTime = 187, + GetScanHomeTime = 188, + SetScanHomeTime = 189, + GetScanNprobes = 190, + SetScanNprobes = 191, + GetPrbRespTimeout = 192, + SetPrbRespTimeout = 193, + GetAtten = 194, + SetAtten = 195, + GetShmem = 196, + SetShmem = 197, + SetWsecTest = 200, + ScbDeauthenticateForReason = 201, + TkipCountermeasures = 202, + GetPiomode = 203, + SetPiomode = 204, + SetAssocPrefer = 205, + GetAssocPrefer = 206, + SetRoamPrefer = 207, + GetRoamPrefer = 208, + SetLed = 209, + GetLed = 210, + GetInterferenceMode = 211, + SetInterferenceMode = 212, + GetChannelQa = 213, + StartChannelQa = 214, + GetChannelSel = 215, + StartChannelSel = 216, + GetValidChannels = 217, + GetFakefrag = 218, + SetFakefrag = 219, + GetPwroutPercentage = 220, + SetPwroutPercentage = 221, + SetBadFramePreempt = 222, + GetBadFramePreempt = 223, + SetLeapList = 224, + GetLeapList = 225, + GetCwmin = 226, + SetCwmin = 227, + GetCwmax = 228, + SetCwmax = 229, + GetWet = 230, + SetWet = 231, + GetPub = 232, + GetKeyPrimary = 235, + SetKeyPrimary = 236, + GetAciArgs = 238, + SetAciArgs = 239, + UnsetCallback = 240, + SetCallback = 241, + GetRadar = 242, + SetRadar = 243, + SetSpectManagment = 244, + GetSpectManagment = 245, + WdsGetRemoteHwaddr = 246, + WdsGetWpaSup = 247, + SetCsScanTimer = 248, + GetCsScanTimer = 249, + MeasureRequest = 250, + Init = 251, + SendQuiet = 252, + Keepalive = 253, + SendPwrConstraint = 254, + UpgradeStatus = 255, + CurrentPwr = 256, + GetScanPassiveTime = 257, + SetScanPassiveTime = 258, + LegacyLinkBehavior = 259, + GetChannelsInCountry = 260, + GetCountryList = 261, + GetVar = 262, + SetVar = 263, + NvramGet = 264, + NvramSet = 265, + NvramDump = 266, + Reboot = 267, + SetWsecPmk = 268, + GetAuthMode = 269, + SetAuthMode = 270, + GetWakeentry = 271, + SetWakeentry = 272, + NdconfigItem = 273, + Nvotpw = 274, + Otpw = 275, + IovBlockGet = 276, + IovModulesGet = 277, + SoftReset = 278, + GetAllowMode = 279, + SetAllowMode = 280, + GetDesiredBssid = 281, + SetDesiredBssid = 282, + DisassocMyap = 283, + GetNbands = 284, + GetBandstates = 285, + GetWlcBssInfo = 286, + GetAssocInfo = 287, + GetOidPhy = 288, + SetOidPhy = 289, + SetAssocTime = 290, + GetDesiredSsid = 291, + GetChanspec = 292, + GetAssocState = 293, + SetPhyState = 294, + GetScanPending = 295, + GetScanreqPending = 296, + GetPrevRoamReason = 297, + SetPrevRoamReason = 298, + GetBandstatesPi = 299, + GetPhyState = 300, + GetBssWpaRsn = 301, + GetBssWpa2Rsn = 302, + GetBssBcnTs = 303, + GetIntDisassoc = 304, + SetNumPeers = 305, + GetNumBss = 306, + GetWsecPmk = 318, + GetRandomBytes = 319, +} diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 22c52bd96..48be772c0 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -109,7 +109,7 @@ impl<'a> Control<'a> { buf[0..8].copy_from_slice(b"clmload\x00"); buf[8..20].copy_from_slice(&header.to_bytes()); buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()]) .await; } @@ -145,7 +145,7 @@ impl<'a> Control<'a> { Timer::after_millis(100).await; // Set antenna to chip antenna - self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await; self.set_iovar_u32("bus:txglom", 0).await; Timer::after_millis(100).await; @@ -183,8 +183,8 @@ impl<'a> Control<'a> { Timer::after_millis(100).await; - self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto - self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any Timer::after_millis(100).await; @@ -195,12 +195,12 @@ impl<'a> Control<'a> { /// Set the WiFi interface up. async fn up(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await; } /// Set the interface down. async fn down(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await; } /// Set power management mode. @@ -213,17 +213,17 @@ impl<'a> Control<'a> { self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; } - self.ioctl_set_u32(86, 0, mode_num).await; + self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await; } /// Join an unprotected network with the provided ssid. pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; - self.ioctl_set_u32(134, 0, 0).await; // wsec = open + self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; // wsec = open self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; // set_auth = open (0) let mut i = SsidInfo { len: ssid.len() as _, @@ -238,24 +238,19 @@ impl<'a> Control<'a> { async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; - self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 + self.ioctl_set_u32(Ioctl::SetWsec, 0, 4).await; // wsec = open self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; Timer::after_millis(100).await; - self.ioctl( - IoctlType::Set, - IOCTL_CMD_SET_PASSPHRASE, - 0, - &mut passphrase_info.to_bytes(), - ) - .await; // WLC_SET_WSEC_PMK + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut passphrase_info.to_bytes()) + .await; // WLC_SET_WSEC_PMK - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) - self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; // set_auth = 0 (open) + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, 0x80).await; let mut i = SsidInfo { len: ssid.len() as _, @@ -294,9 +289,7 @@ impl<'a> Control<'a> { // the actual join operation starts here // we make sure to enable events before so we don't miss any - // set_ssid - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) - .await; + self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await; // to complete the join, we wait for a SET_SSID event // we also save the AUTH status for the user, it may be interesting @@ -357,7 +350,7 @@ impl<'a> Control<'a> { self.up().await; // Turn on AP mode - self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await; // Set SSID let mut i = SsidInfoWithIndex { @@ -371,7 +364,7 @@ impl<'a> Control<'a> { self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; // Set channel number - self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; + self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await; // Set security self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; @@ -388,7 +381,7 @@ impl<'a> Control<'a> { passphrase: [0; 64], }; pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) .await; } @@ -405,7 +398,7 @@ impl<'a> Control<'a> { self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN // Turn off AP mode - self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await; // Temporarily set wifi down self.down().await; @@ -496,7 +489,7 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) .await; } @@ -510,7 +503,7 @@ impl<'a> Control<'a> { let total_len = max(name.len() + 1, res.len()); let res_len = self - .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .ioctl(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) .await; let out_len = min(res.len(), res_len); @@ -518,12 +511,12 @@ impl<'a> Control<'a> { out_len } - async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) { let mut buf = val.to_le_bytes(); self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; } - async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { struct CancelOnDrop<'a>(&'a IoctlState); impl CancelOnDrop<'_> { @@ -615,7 +608,7 @@ impl<'a> Control<'a> { } /// Leave the wifi, with which we are currently associated. pub async fn leave(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await; info!("Disassociated") } diff --git a/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs index 61524c274..3186370cc 100644 --- a/cyw43/src/ioctl.rs +++ b/cyw43/src/ioctl.rs @@ -4,6 +4,7 @@ use core::task::{Poll, Waker}; use embassy_sync::waitqueue::WakerRegistration; +use crate::consts::Ioctl; use crate::fmt::Bytes; #[derive(Clone, Copy)] @@ -16,7 +17,7 @@ pub enum IoctlType { pub struct PendingIoctl { pub buf: *mut [u8], pub kind: IoctlType, - pub cmd: u32, + pub cmd: Ioctl, pub iface: u32, } @@ -101,7 +102,7 @@ impl IoctlState { self.state.set(IoctlStateInner::Done { resp_len: 0 }); } - pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { self.state .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); self.wake_runner(); diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs index 959718341..77910b281 100644 --- a/cyw43/src/runner.rs +++ b/cyw43/src/runner.rs @@ -560,7 +560,7 @@ where self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 } - async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8], buf: &mut [u32; 512]) { + async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) { let buf8 = slice8_mut(buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); @@ -582,7 +582,7 @@ where }; let cdc_header = CdcHeader { - cmd: cmd, + cmd: cmd as u32, len: data.len() as _, flags: kind as u16 | (iface as u16) << 12, id: self.ioctl_id, From 6b21f6d3d1f48bfa722d648918e06b627350bbff Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 9 Sep 2024 01:03:41 +0200 Subject: [PATCH 065/127] cyw43: log ioctls. --- cyw43/src/control.rs | 16 ++++++++++++---- cyw43/src/ioctl.rs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 48be772c0..97dcb4d09 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -481,7 +481,7 @@ impl<'a> Control<'a> { } async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { - debug!("set {} = {:02x}", name, Bytes(val)); + debug!("iovar set {} = {:02x}", name, Bytes(val)); let mut buf = [0; BUFSIZE]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -489,13 +489,13 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) + self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) .await; } // TODO this is not really working, it always returns all zeros. async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { - debug!("get {}", name); + debug!("iovar get {}", name); let mut buf = [0; 64]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -503,7 +503,7 @@ impl<'a> Control<'a> { let total_len = max(name.len() + 1, res.len()); let res_len = self - .ioctl(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) + .ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) .await; let out_len = min(res.len(), res_len); @@ -517,6 +517,14 @@ impl<'a> Control<'a> { } async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + if kind == IoctlType::Set { + debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf)); + } + let n = self.ioctl_inner(kind, cmd, iface, buf).await; + n + } + + async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { struct CancelOnDrop<'a>(&'a IoctlState); impl CancelOnDrop<'_> { diff --git a/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs index 3186370cc..f8b2d9aba 100644 --- a/cyw43/src/ioctl.rs +++ b/cyw43/src/ioctl.rs @@ -7,7 +7,7 @@ use embassy_sync::waitqueue::WakerRegistration; use crate::consts::Ioctl; use crate::fmt::Bytes; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum IoctlType { Get = 0, Set = 2, From b9a1aaea5b89bd5689796bdfa4227353ee8a452b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 9 Sep 2024 01:09:15 +0200 Subject: [PATCH 066/127] cyw43: add support for WPA3 and more extensive security options. --- cyw43/src/consts.rs | 15 ++ cyw43/src/control.rs | 196 ++++++++++++++++++------- cyw43/src/lib.rs | 4 +- cyw43/src/structs.rs | 9 ++ examples/rp/src/bin/wifi_tcp_server.rs | 7 +- examples/rp/src/bin/wifi_webrequest.rs | 7 +- tests/rp/src/bin/cyw43-perf.rs | 6 +- 7 files changed, 181 insertions(+), 63 deletions(-) diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs index d47e5097a..c3f0dbfd8 100644 --- a/cyw43/src/consts.rs +++ b/cyw43/src/consts.rs @@ -653,3 +653,18 @@ pub(crate) enum Ioctl { GetWsecPmk = 318, GetRandomBytes = 319, } + +pub(crate) const WSEC_TKIP: u32 = 0x02; +pub(crate) const WSEC_AES: u32 = 0x04; + +pub(crate) const AUTH_OPEN: u32 = 0x00; +pub(crate) const AUTH_SAE: u32 = 0x03; + +pub(crate) const MFP_NONE: u32 = 0; +pub(crate) const MFP_CAPABLE: u32 = 1; +pub(crate) const MFP_REQUIRED: u32 = 2; + +pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000; +pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004; +pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080; +pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000; diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 97dcb4d09..071ba88e4 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -35,7 +35,7 @@ pub struct Control<'a> { ioctl_state: &'a IoctlState, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ScanType { Active, @@ -43,8 +43,9 @@ pub enum ScanType { } /// Scan options. -#[derive(Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] pub struct ScanOptions { /// SSID to scan for. pub ssid: Option>, @@ -74,6 +75,79 @@ impl Default for ScanOptions { } } +/// Authentication type, used in [`JoinOptions::auth`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum JoinAuth { + /// Open network + Open, + /// WPA only + Wpa, + /// WPA2 only + Wpa2, + /// WPA3 only + Wpa3, + /// WPA2 + WPA3 + Wpa2Wpa3, +} + +/// Options for [`Control::join`]. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct JoinOptions<'a> { + /// Authentication type. Default `Wpa2Wpa3`. + pub auth: JoinAuth, + /// Enable TKIP encryption. Default false. + pub cipher_tkip: bool, + /// Enable AES encryption. Default true. + pub cipher_aes: bool, + /// Passphrase. Default empty. + pub passphrase: &'a [u8], + /// If false, `passphrase` is the human-readable passphrase string. + /// If true, `passphrase` is the result of applying the PBKDF2 hash to the + /// passphrase string. This makes it possible to avoid storing unhashed passwords. + /// + /// This is not compatible with WPA3. + /// Default false. + pub passphrase_is_prehashed: bool, +} + +impl<'a> JoinOptions<'a> { + /// Create a new `JoinOptions` for joining open networks. + pub fn new_open() -> Self { + Self { + auth: JoinAuth::Open, + cipher_tkip: false, + cipher_aes: false, + passphrase: &[], + passphrase_is_prehashed: false, + } + } + + /// Create a new `JoinOptions` for joining encrypted networks. + /// + /// Defaults to supporting WPA2+WPA3 with AES only, you may edit + /// the returned options to change this. + pub fn new(passphrase: &'a [u8]) -> Self { + let mut this = Self::default(); + this.passphrase = passphrase; + this + } +} + +impl<'a> Default for JoinOptions<'a> { + fn default() -> Self { + Self { + auth: JoinAuth::Wpa2Wpa3, + cipher_tkip: false, + cipher_aes: true, + passphrase: &[], + passphrase_is_prehashed: false, + } + } +} + impl<'a> Control<'a> { pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { Self { @@ -217,13 +291,70 @@ impl<'a> Control<'a> { } /// Join an unprotected network with the provided ssid. - pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { + pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; - self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; // wsec = open - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; - self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; // set_auth = open (0) + if options.auth == JoinAuth::Open { + self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await; + } else { + let mut wsec = 0; + if options.cipher_aes { + wsec |= WSEC_AES; + } + if options.cipher_tkip { + wsec |= WSEC_TKIP; + } + self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await; + + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after_millis(100).await; + + let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth { + JoinAuth::Open => unreachable!(), + JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK), + JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK), + JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK), + JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK), + }; + + if wpa12 { + let mut flags = 0; + if !options.passphrase_is_prehashed { + flags |= 1; + } + let mut pfi = PassphraseInfo { + len: options.passphrase.len() as _, + flags, + passphrase: [0; 64], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + if wpa3 { + let mut pfi = SaePassphraseInfo { + len: options.passphrase.len() as _, + passphrase: [0; 128], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.set_iovar("sae_password", &pfi.to_bytes()).await; + } + + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await; + self.set_iovar_u32("mfp", mfp).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await; + } let mut i = SsidInfo { len: ssid.len() as _, @@ -234,55 +365,6 @@ impl<'a> Control<'a> { self.wait_for_join(i).await } - /// Join a protected network with the provided ssid and [`PassphraseInfo`]. - async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> { - self.set_iovar_u32("ampdu_ba_wsize", 8).await; - - self.ioctl_set_u32(Ioctl::SetWsec, 0, 4).await; // wsec = open - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; - self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; - self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; - - Timer::after_millis(100).await; - - self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut passphrase_info.to_bytes()) - .await; // WLC_SET_WSEC_PMK - - self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; // set_auth = 0 (open) - self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, 0x80).await; - - let mut i = SsidInfo { - len: ssid.len() as _, - ssid: [0; 32], - }; - i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - - self.wait_for_join(i).await - } - - /// Join a protected network with the provided ssid and passphrase. - pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { - let mut pfi = PassphraseInfo { - len: passphrase.len() as _, - flags: 1, - passphrase: [0; 64], - }; - pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); - self.join_wpa2_passphrase_info(ssid, &pfi).await - } - - /// Join a protected network with the provided ssid and precomputed PSK. - pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> { - let mut pfi = PassphraseInfo { - len: psk.len() as _, - flags: 0, - passphrase: [0; 64], - }; - pfi.passphrase[..psk.len()].copy_from_slice(psk); - self.join_wpa2_passphrase_info(ssid, &pfi).await - } - async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); let mut subscriber = self.events.queue.subscriber().unwrap(); @@ -477,7 +559,7 @@ impl<'a> Control<'a> { } async fn set_iovar(&mut self, name: &str, val: &[u8]) { - self.set_iovar_v::<64>(name, val).await + self.set_iovar_v::<196>(name, val).await } async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs index efeb3f313..6b71c18e6 100644 --- a/cyw43/src/lib.rs +++ b/cyw43/src/lib.rs @@ -28,7 +28,9 @@ use ioctl::IoctlState; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; -pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, ScanOptions, Scanner}; +pub use crate::control::{ + AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, Scanner, +}; pub use crate::runner::Runner; pub use crate::structs::BssInfo; diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs index ae7ef6038..81ae6a98d 100644 --- a/cyw43/src/structs.rs +++ b/cyw43/src/structs.rs @@ -394,6 +394,15 @@ pub struct PassphraseInfo { } impl_bytes!(PassphraseInfo); +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SaePassphraseInfo { + pub len: u16, + pub passphrase: [u8; 128], +} +impl_bytes!(SaePassphraseInfo); + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs index 61eeb82f7..b2950d98a 100644 --- a/examples/rp/src/bin/wifi_tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -7,6 +7,7 @@ use core::str::from_utf8; +use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; @@ -95,8 +96,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(net_task(stack))); loop { - //control.join_open(WIFI_NETWORK).await; - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/examples/rp/src/bin/wifi_webrequest.rs b/examples/rp/src/bin/wifi_webrequest.rs index 889371241..b43be8905 100644 --- a/examples/rp/src/bin/wifi_webrequest.rs +++ b/examples/rp/src/bin/wifi_webrequest.rs @@ -7,6 +7,7 @@ use core::str::from_utf8; +use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; @@ -98,8 +99,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(net_task(stack))); loop { - //match control.join_open(WIFI_NETWORK).await { // for open networks - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index 38fbde7c1..11c8aa58c 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -2,6 +2,7 @@ #![no_main] teleprobe_meta::target!(b"rpi-pico"); +use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::{panic, *}; use embassy_executor::Spawner; @@ -81,7 +82,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(net_task(stack))); loop { - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { panic!("join failed with status={}", err.status); From 6af1cb7a20fe15750b353e2e6af6832786f8c065 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 9 Sep 2024 15:45:15 +0200 Subject: [PATCH 067/127] Use TX_BUF_SIZE matching MTU --- embassy-net-nrf91/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs index d8cbe47fc..60cdc38c6 100644 --- a/embassy-net-nrf91/src/lib.rs +++ b/embassy-net-nrf91/src/lib.rs @@ -279,7 +279,7 @@ impl State { } const TX_BUF_COUNT: usize = 4; -const TX_BUF_SIZE: usize = 1024; +const TX_BUF_SIZE: usize = 1500; struct TraceChannelInfo { ptr: *mut TraceChannel, From e698fbe598def25a1aa3e2a2625144817f2fa12d Mon Sep 17 00:00:00 2001 From: elagil Date: Tue, 10 Sep 2024 21:33:31 +0200 Subject: [PATCH 068/127] fix: pull-down clock/data lines for receive --- embassy-stm32/src/sai/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index 6bf184dd8..63f48ace0 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -661,12 +661,12 @@ fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AfType, AfType) { //sd is defined by tx/rx mode match tx_rx { TxRx::Transmitter => AfType::output(OutputType::PushPull, Speed::VeryHigh), - TxRx::Receiver => AfType::input(Pull::None), + TxRx::Receiver => AfType::input(Pull::Down), // Ensure mute level when no input is connected. }, //clocks (mclk, sck and fs) are defined by master/slave match mode { Mode::Master => AfType::output(OutputType::PushPull, Speed::VeryHigh), - Mode::Slave => AfType::input(Pull::None), + Mode::Slave => AfType::input(Pull::Down), // Ensure no clocks when no input is connected. }, ) } From a1c9a2e8bd83852d774255c82e71d2054c7c02e4 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 29 Aug 2024 16:18:43 -0400 Subject: [PATCH 069/127] First working draft of a lptim driver This driver is able to PWM a pin --- embassy-stm32/build.rs | 2 + embassy-stm32/src/lib.rs | 2 + embassy-stm32/src/lptim/mod.rs | 145 ++++++++++++++++++++++++++++++ embassy-stm32/src/lptim/traits.rs | 95 ++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 embassy-stm32/src/lptim/mod.rs create mode 100644 embassy-stm32/src/lptim/traits.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 1984a1420..33985a3f4 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1015,6 +1015,8 @@ fn main() { (("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)), (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), + (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "CH2"), quote!(crate::lptim::Channel1Pin)), (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), (("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)), diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 98695e738..451f595e0 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -89,6 +89,8 @@ pub mod i2s; pub mod ipcc; #[cfg(feature = "low-power")] pub mod low_power; +#[cfg(lptim)] +pub mod lptim; #[cfg(ltdc)] pub mod ltdc; #[cfg(opamp)] diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs new file mode 100644 index 000000000..3cf95149e --- /dev/null +++ b/embassy-stm32/src/lptim/mod.rs @@ -0,0 +1,145 @@ +//! Low-power timer (LPTIM) + +mod traits; + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use traits::Instance; + +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +// use crate::time::Hertz; +use crate::{rcc, Peripheral}; + +/// LPTIM master instance. +pub struct Master { + phantom: PhantomData, +} + +/// LPTIM channel 1 instance. +pub struct Ch1 { + phantom: PhantomData, +} + +/// LPTIM channel 2 instance. +pub struct Ch2 { + phantom: PhantomData, +} + +trait SealedChannel { + fn raw() -> usize; +} + +/// channel instance trait. +#[allow(private_bounds)] +pub trait Channel: SealedChannel {} + +/// LPTIM PWM pin. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident) => { + impl<'d, T: Instance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + PwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + + impl SealedChannel for $channel { + fn raw() -> usize { + $ch_num + } + } + impl Channel for $channel {} + }; +} + +channel_impl!(new_ch1, Ch1, 0, Channel1Pin); +channel_impl!(new_ch2, Ch2, 1, Channel2Pin); + +/// Struct used to divide a high resolution timer into multiple channels +pub struct Pwm<'d, T: Instance> { + _inner: PeripheralRef<'d, T>, + /// Master instance. + pub master: Master, + /// Channel 1. + pub ch_1: Ch1, + /// Channel 2. + pub ch_2: Ch2, +} + +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new LPTIM driver. + /// + /// This splits the LPTIM into its constituent parts, which you can then use individually. + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1: Option>>, + _ch2: Option>>, + ) -> Self { + Self::new_inner(tim) + } + + fn new_inner(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + T::regs().cr().modify(|w| w.set_enable(true)); + + // Set frequency. Should be configurable. + // By default, 16MHz. We want 440Hz. + // That's 36363 cycles + T::regs().arr().write_value(stm32_metapac::lptim::regs::Arr(36363)); + + // Set duty cycle. Should be configurable. Should take care of channel too (only Ch1 now) + T::regs().ccr(0).write_value(stm32_metapac::lptim::regs::Ccr(18181)); + + // Enable channel as PWM. Default state anyway. Implement later. + // T::regs().ccmr().modify(|w| { + // w.set_ccsel(0, 0); + // w.set_ccsel(1, 0); + // }) + + // Enable output on pins. Should care about the channels! + T::regs().ccmr().modify(|w| { + w.set_cce(0, true); + w.set_cce(1, true); + }); + + Self { + _inner: tim, + master: Master { phantom: PhantomData }, + ch_1: Ch1 { phantom: PhantomData }, + ch_2: Ch2 { phantom: PhantomData }, + } + } + + /// Start + pub fn start(&mut self) { + T::regs().cr().modify(|w| w.set_cntstrt(true)); + } + + /// Stop + pub fn stop(&mut self) { + T::regs().cr().modify(|w| w.set_cntstrt(false)); + } +} + +pin_trait!(Channel1Pin, Instance); +pin_trait!(Channel2Pin, Instance); diff --git a/embassy-stm32/src/lptim/traits.rs b/embassy-stm32/src/lptim/traits.rs new file mode 100644 index 000000000..a8f890a78 --- /dev/null +++ b/embassy-stm32/src/lptim/traits.rs @@ -0,0 +1,95 @@ +use crate::rcc::RccPeripheral; +use crate::time::Hertz; + +#[repr(u8)] +#[derive(Clone, Copy)] +pub(crate) enum Prescaler { + Div1 = 1, + Div2 = 2, + Div4 = 4, + Div8 = 8, + Div16 = 16, + Div32 = 32, + Div64 = 64, + Div128 = 128, +} + +impl From for u8 { + fn from(val: Prescaler) -> Self { + match val { + Prescaler::Div1 => 0b000, + Prescaler::Div2 => 0b001, + Prescaler::Div4 => 0b010, + Prescaler::Div8 => 0b011, + Prescaler::Div16 => 0b100, + Prescaler::Div32 => 0b101, + Prescaler::Div64 => 0b110, + Prescaler::Div128 => 0b111, + } + } +} + +impl From for Prescaler { + fn from(val: u8) -> Self { + match val { + 0b000 => Prescaler::Div1, + 0b001 => Prescaler::Div2, + 0b010 => Prescaler::Div4, + 0b011 => Prescaler::Div8, + 0b100 => Prescaler::Div16, + 0b101 => Prescaler::Div32, + 0b110 => Prescaler::Div64, + 0b111 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn compute_min_high_res(val: u32) -> Self { + *[ + Prescaler::Div1, + Prescaler::Div2, + Prescaler::Div4, + Prescaler::Div8, + Prescaler::Div16, + Prescaler::Div32, + Prescaler::Div64, + Prescaler::Div128, + ] + .iter() + .skip_while(|psc| **psc as u32 <= val) + .next() + .unwrap() + } + + pub fn compute_min_low_res(val: u32) -> Self { + *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] + .iter() + .skip_while(|psc| **psc as u32 <= val) + .next() + .unwrap() + } +} + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::lptim::LptimAdv; +} + +/// LPTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static {} + +foreach_interrupt! { + ($inst:ident, lptim, LPTIM_ADV, UP, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::lptim::LptimAdv { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + + } + }; +} From 3b8859653e4028c194ceed9432a4f76c3c856816 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Sep 2024 14:46:35 -0400 Subject: [PATCH 070/127] stm32: Clean up the lptim driver --- embassy-stm32/src/lptim/mod.rs | 166 +++++------------------ embassy-stm32/src/lptim/pwm.rs | 122 +++++++++++++++++ embassy-stm32/src/lptim/timer.rs | 212 ++++++++++++++++++++++++++++++ embassy-stm32/src/lptim/traits.rs | 95 ------------- 4 files changed, 369 insertions(+), 226 deletions(-) create mode 100644 embassy-stm32/src/lptim/pwm.rs create mode 100644 embassy-stm32/src/lptim/timer.rs delete mode 100644 embassy-stm32/src/lptim/traits.rs diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index 3cf95149e..baedf9fe2 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -1,145 +1,49 @@ //! Low-power timer (LPTIM) -mod traits; +pub mod pwm; +pub mod timer; -use core::marker::PhantomData; +use crate::rcc::RccPeripheral; -use embassy_hal_internal::{into_ref, PeripheralRef}; -pub use traits::Instance; - -use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -// use crate::time::Hertz; -use crate::{rcc, Peripheral}; - -/// LPTIM master instance. -pub struct Master { - phantom: PhantomData, -} - -/// LPTIM channel 1 instance. -pub struct Ch1 { - phantom: PhantomData, -} - -/// LPTIM channel 2 instance. -pub struct Ch2 { - phantom: PhantomData, -} - -trait SealedChannel { - fn raw() -> usize; -} - -/// channel instance trait. -#[allow(private_bounds)] -pub trait Channel: SealedChannel {} - -/// LPTIM PWM pin. -pub struct PwmPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, - phantom: PhantomData<(T, C)>, -} - -macro_rules! channel_impl { - ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident) => { - impl<'d, T: Instance> PwmPin<'d, T, $channel> { - #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output(OutputType::PushPull, Speed::VeryHigh), - ); - }); - PwmPin { - _pin: pin.map_into(), - phantom: PhantomData, - } - } - } - - impl SealedChannel for $channel { - fn raw() -> usize { - $ch_num - } - } - impl Channel for $channel {} - }; -} - -channel_impl!(new_ch1, Ch1, 0, Channel1Pin); -channel_impl!(new_ch2, Ch2, 1, Channel2Pin); - -/// Struct used to divide a high resolution timer into multiple channels -pub struct Pwm<'d, T: Instance> { - _inner: PeripheralRef<'d, T>, - /// Master instance. - pub master: Master, +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { /// Channel 1. - pub ch_1: Ch1, + Ch1, /// Channel 2. - pub ch_2: Ch2, + Ch2, } -impl<'d, T: Instance> Pwm<'d, T> { - /// Create a new LPTIM driver. - /// - /// This splits the LPTIM into its constituent parts, which you can then use individually. - pub fn new( - tim: impl Peripheral

+ 'd, - _ch1: Option>>, - _ch2: Option>>, - ) -> Self { - Self::new_inner(tim) - } - - fn new_inner(tim: impl Peripheral

+ 'd) -> Self { - into_ref!(tim); - - rcc::enable_and_reset::(); - - T::regs().cr().modify(|w| w.set_enable(true)); - - // Set frequency. Should be configurable. - // By default, 16MHz. We want 440Hz. - // That's 36363 cycles - T::regs().arr().write_value(stm32_metapac::lptim::regs::Arr(36363)); - - // Set duty cycle. Should be configurable. Should take care of channel too (only Ch1 now) - T::regs().ccr(0).write_value(stm32_metapac::lptim::regs::Ccr(18181)); - - // Enable channel as PWM. Default state anyway. Implement later. - // T::regs().ccmr().modify(|w| { - // w.set_ccsel(0, 0); - // w.set_ccsel(1, 0); - // }) - - // Enable output on pins. Should care about the channels! - T::regs().ccmr().modify(|w| { - w.set_cce(0, true); - w.set_cce(1, true); - }); - - Self { - _inner: tim, - master: Master { phantom: PhantomData }, - ch_1: Ch1 { phantom: PhantomData }, - ch_2: Ch2 { phantom: PhantomData }, +impl Channel { + /// Get the channel index (0..1) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, } } - - /// Start - pub fn start(&mut self) { - T::regs().cr().modify(|w| w.set_cntstrt(true)); - } - - /// Stop - pub fn stop(&mut self) { - T::regs().cr().modify(|w| w.set_cntstrt(false)); - } } pin_trait!(Channel1Pin, Instance); pin_trait!(Channel2Pin, Instance); + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::lptim::LptimAdv; +} + +/// LPTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static {} +foreach_interrupt! { + ($inst:ident, lptim, LPTIM_ADV, UP, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::lptim::LptimAdv { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + + } + }; +} diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs new file mode 100644 index 000000000..725aa676e --- /dev/null +++ b/embassy-stm32/src/lptim/pwm.rs @@ -0,0 +1,122 @@ +//! PWM driver. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::timer::{ChannelDirection, Timer}; +use super::{Channel, Channel1Pin, Channel2Pin, Instance}; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: Instance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + PwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); + +/// PWM driver. +pub struct Pwm<'d, T: Instance> { + inner: Timer<'d, T>, // _inner: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1_pin: Option>, + _ch2_pin: Option>, + freq: Hertz, + ) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.enable(); + this.set_frequency(freq); + + [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { + this.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); + }); + + this.inner.continuous_mode_start(); + + this + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self, channel: Channel) -> bool { + self.inner.get_channel_enable_state(channel) + } + + /// Set PWM frequency. + /// + /// Note: when you call this, the max duty value changes, so you will have to + /// call `set_duty` on all channels with the duty calculated based on the new max duty. + pub fn set_frequency(&mut self, frequency: Hertz) { + self.inner.set_frequency(frequency); + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn get_max_duty(&self) -> u16 { + self.inner.get_max_compare_value() + 1 + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, channel: Channel, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(channel, duty) + } + + /// Get the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self, channel: Channel) -> u16 { + self.inner.get_compare_value(channel) + } +} diff --git a/embassy-stm32/src/lptim/timer.rs b/embassy-stm32/src/lptim/timer.rs new file mode 100644 index 000000000..06392b2e7 --- /dev/null +++ b/embassy-stm32/src/lptim/timer.rs @@ -0,0 +1,212 @@ +//! Low-level timer driver. + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +use super::{Channel, Instance}; +use crate::pac::lptim::vals; +use crate::rcc; +use crate::time::Hertz; + +/// Direction of a low-power timer channel +pub enum ChannelDirection { + /// Use channel as a PWM output + OutputPwm, + /// Use channel as an input capture + InputCapture, +} + +impl From for vals::Ccsel { + fn from(direction: ChannelDirection) -> Self { + match direction { + ChannelDirection::OutputPwm => vals::Ccsel::OUTPUTCOMPARE, + ChannelDirection::InputCapture => vals::Ccsel::INPUTCAPTURE, + } + } +} + +enum Prescaler { + Div1, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl From<&Prescaler> for vals::Presc { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => vals::Presc::DIV1, + Prescaler::Div2 => vals::Presc::DIV2, + Prescaler::Div4 => vals::Presc::DIV4, + Prescaler::Div8 => vals::Presc::DIV8, + Prescaler::Div16 => vals::Presc::DIV16, + Prescaler::Div32 => vals::Presc::DIV32, + Prescaler::Div64 => vals::Presc::DIV64, + Prescaler::Div128 => vals::Presc::DIV128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: vals::Presc) -> Self { + match prescaler { + vals::Presc::DIV1 => Prescaler::Div1, + vals::Presc::DIV2 => Prescaler::Div2, + vals::Presc::DIV4 => Prescaler::Div4, + vals::Presc::DIV8 => Prescaler::Div8, + vals::Presc::DIV16 => Prescaler::Div16, + vals::Presc::DIV32 => Prescaler::Div32, + vals::Presc::DIV64 => Prescaler::Div64, + vals::Presc::DIV128 => Prescaler::Div128, + } + } +} + +impl From<&Prescaler> for u32 { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => 1, + Prescaler::Div2 => 2, + Prescaler::Div4 => 4, + Prescaler::Div8 => 8, + Prescaler::Div16 => 16, + Prescaler::Div32 => 32, + Prescaler::Div64 => 64, + Prescaler::Div128 => 128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: u32) -> Self { + match prescaler { + 1 => Prescaler::Div1, + 2 => Prescaler::Div2, + 4 => Prescaler::Div4, + 8 => Prescaler::Div8, + 16 => Prescaler::Div16, + 32 => Prescaler::Div32, + 64 => Prescaler::Div64, + 128 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn from_ticks(ticks: u32) -> Self { + // We need to scale down to a 16-bit range + (ticks >> 16).next_power_of_two().into() + } + + pub fn scale_down(&self, ticks: u32) -> u16 { + (ticks / u32::from(self)).try_into().unwrap() + } + + pub fn scale_up(&self, ticks: u16) -> u32 { + u32::from(self) * ticks as u32 + } +} + +/// Low-level timer driver. +pub struct Timer<'d, T: Instance> { + _tim: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Timer<'d, T> { + /// Create a new timer driver. + pub fn new(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + Self { _tim: tim } + } + + /// Enable the timer. + pub fn enable(&self) { + T::regs().cr().modify(|w| w.set_enable(true)); + } + + /// Disable the timer. + pub fn disable(&self) { + T::regs().cr().modify(|w| w.set_enable(false)); + } + + /// Start the timer in single pulse mode. + pub fn single_mode_start(&self) { + T::regs().cr().modify(|w| w.set_sngstrt(true)); + } + + /// Start the timer in continuous mode. + pub fn continuous_mode_start(&self) { + T::regs().cr().modify(|w| w.set_cntstrt(true)); + } + + /// Set channel direction. + pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { + T::regs() + .ccmr() + .modify(|w| w.set_ccsel(channel.index(), direction.into())); + } + + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. + pub fn set_frequency(&self, frequency: Hertz) { + let f = frequency.0; + assert!(f > 0); + + let pclk_f = T::frequency().0; + + let pclk_ticks_per_timer_period = pclk_f / f; + + let psc = Prescaler::from_ticks(pclk_ticks_per_timer_period); + let arr = psc.scale_down(pclk_ticks_per_timer_period); + + T::regs().cfgr().modify(|r| r.set_presc((&psc).into())); + T::regs().arr().modify(|r| r.set_arr(arr.into())); + } + + /// Get the timer frequency. + pub fn get_frequency(&self) -> Hertz { + let pclk_f = T::frequency(); + let arr = T::regs().arr().read().arr(); + let psc = Prescaler::from(T::regs().cfgr().read().presc()); + + pclk_f / psc.scale_up(arr) + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } + + /// Enable/disable a channel. + pub fn enable_channel(&self, channel: Channel, enable: bool) { + T::regs().ccmr().modify(|w| { + w.set_cce(channel.index(), enable); + }); + } + + /// Get enable/disable state of a channel + pub fn get_channel_enable_state(&self, channel: Channel) -> bool { + T::regs().ccmr().read().cce(channel.index()) + } + + /// Set compare value for a channel. + pub fn set_compare_value(&self, channel: Channel, value: u16) { + T::regs().ccr(channel.index()).modify(|w| w.set_ccr(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self, channel: Channel) -> u16 { + T::regs().ccr(channel.index()).read().ccr() + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u16 { + T::regs().arr().read().arr() + } +} diff --git a/embassy-stm32/src/lptim/traits.rs b/embassy-stm32/src/lptim/traits.rs deleted file mode 100644 index a8f890a78..000000000 --- a/embassy-stm32/src/lptim/traits.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::rcc::RccPeripheral; -use crate::time::Hertz; - -#[repr(u8)] -#[derive(Clone, Copy)] -pub(crate) enum Prescaler { - Div1 = 1, - Div2 = 2, - Div4 = 4, - Div8 = 8, - Div16 = 16, - Div32 = 32, - Div64 = 64, - Div128 = 128, -} - -impl From for u8 { - fn from(val: Prescaler) -> Self { - match val { - Prescaler::Div1 => 0b000, - Prescaler::Div2 => 0b001, - Prescaler::Div4 => 0b010, - Prescaler::Div8 => 0b011, - Prescaler::Div16 => 0b100, - Prescaler::Div32 => 0b101, - Prescaler::Div64 => 0b110, - Prescaler::Div128 => 0b111, - } - } -} - -impl From for Prescaler { - fn from(val: u8) -> Self { - match val { - 0b000 => Prescaler::Div1, - 0b001 => Prescaler::Div2, - 0b010 => Prescaler::Div4, - 0b011 => Prescaler::Div8, - 0b100 => Prescaler::Div16, - 0b101 => Prescaler::Div32, - 0b110 => Prescaler::Div64, - 0b111 => Prescaler::Div128, - _ => unreachable!(), - } - } -} - -impl Prescaler { - pub fn compute_min_high_res(val: u32) -> Self { - *[ - Prescaler::Div1, - Prescaler::Div2, - Prescaler::Div4, - Prescaler::Div8, - Prescaler::Div16, - Prescaler::Div32, - Prescaler::Div64, - Prescaler::Div128, - ] - .iter() - .skip_while(|psc| **psc as u32 <= val) - .next() - .unwrap() - } - - pub fn compute_min_low_res(val: u32) -> Self { - *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] - .iter() - .skip_while(|psc| **psc as u32 <= val) - .next() - .unwrap() - } -} - -pub(crate) trait SealedInstance: RccPeripheral { - fn regs() -> crate::pac::lptim::LptimAdv; -} - -/// LPTIM instance trait. -#[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static {} - -foreach_interrupt! { - ($inst:ident, lptim, LPTIM_ADV, UP, $irq:ident) => { - impl SealedInstance for crate::peripherals::$inst { - fn regs() -> crate::pac::lptim::LptimAdv { - crate::pac::$inst - } - } - - impl Instance for crate::peripherals::$inst { - - } - }; -} From b449e04a88d76b41aae91d98bd66bf2754637d03 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Sep 2024 18:06:46 -0400 Subject: [PATCH 071/127] stm32: Temporary fix around incomplete stm32-metapac --- embassy-stm32/src/lptim/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index baedf9fe2..327a1d44b 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -35,7 +35,7 @@ pub(crate) trait SealedInstance: RccPeripheral { #[allow(private_bounds)] pub trait Instance: SealedInstance + 'static {} foreach_interrupt! { - ($inst:ident, lptim, LPTIM_ADV, UP, $irq:ident) => { + ($inst:ident, lptim, LPTIM_ADV, GLOBAL, $irq:ident) => { impl SealedInstance for crate::peripherals::$inst { fn regs() -> crate::pac::lptim::LptimAdv { crate::pac::$inst From 37130f45e4f74f7b4faeb7cac017c4588e5b422a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 5 Sep 2024 11:39:09 -0400 Subject: [PATCH 072/127] stm32: Use the updated LPTIM pac --- embassy-stm32/src/lptim/mod.rs | 6 +++--- embassy-stm32/src/lptim/pwm.rs | 1 + embassy-stm32/src/lptim/timer.rs | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index 327a1d44b..dc5f1d5bb 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -28,16 +28,16 @@ pin_trait!(Channel1Pin, Instance); pin_trait!(Channel2Pin, Instance); pub(crate) trait SealedInstance: RccPeripheral { - fn regs() -> crate::pac::lptim::LptimAdv; + fn regs() -> crate::pac::lptim::Lptim; } /// LPTIM instance trait. #[allow(private_bounds)] pub trait Instance: SealedInstance + 'static {} foreach_interrupt! { - ($inst:ident, lptim, LPTIM_ADV, GLOBAL, $irq:ident) => { + ($inst:ident, lptim, LPTIM, UP, $irq:ident) => { impl SealedInstance for crate::peripherals::$inst { - fn regs() -> crate::pac::lptim::LptimAdv { + fn regs() -> crate::pac::lptim::Lptim { crate::pac::$inst } } diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs index 725aa676e..24725f625 100644 --- a/embassy-stm32/src/lptim/pwm.rs +++ b/embassy-stm32/src/lptim/pwm.rs @@ -66,6 +66,7 @@ impl<'d, T: Instance> Pwm<'d, T> { this.inner.enable(); this.set_frequency(freq); + #[cfg(any(lptim_v2a, lptim_v2b))] [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { this.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); }); diff --git a/embassy-stm32/src/lptim/timer.rs b/embassy-stm32/src/lptim/timer.rs index 06392b2e7..b354c1f61 100644 --- a/embassy-stm32/src/lptim/timer.rs +++ b/embassy-stm32/src/lptim/timer.rs @@ -8,6 +8,7 @@ use crate::rcc; use crate::time::Hertz; /// Direction of a low-power timer channel +#[cfg(any(lptim_v2a, lptim_v2b))] pub enum ChannelDirection { /// Use channel as a PWM output OutputPwm, @@ -15,6 +16,7 @@ pub enum ChannelDirection { InputCapture, } +#[cfg(any(lptim_v2a, lptim_v2b))] impl From for vals::Ccsel { fn from(direction: ChannelDirection) -> Self { match direction { @@ -147,9 +149,10 @@ impl<'d, T: Instance> Timer<'d, T> { } /// Set channel direction. + #[cfg(any(lptim_v2a, lptim_v2b))] pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { T::regs() - .ccmr() + .ccmr(0) .modify(|w| w.set_ccsel(channel.index(), direction.into())); } @@ -185,14 +188,14 @@ impl<'d, T: Instance> Timer<'d, T> { /// Enable/disable a channel. pub fn enable_channel(&self, channel: Channel, enable: bool) { - T::regs().ccmr().modify(|w| { + T::regs().ccmr(0).modify(|w| { w.set_cce(channel.index(), enable); }); } /// Get enable/disable state of a channel pub fn get_channel_enable_state(&self, channel: Channel) -> bool { - T::regs().ccmr().read().cce(channel.index()) + T::regs().ccmr(0).read().cce(channel.index()) } /// Set compare value for a channel. From 652133bce48f39ea5f379d056ca46a7f2ca3a99a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 5 Sep 2024 15:10:39 -0400 Subject: [PATCH 073/127] stm32: Support LPTIM v1 and v2 --- embassy-stm32/build.rs | 1 + embassy-stm32/src/lptim/channel.rs | 18 ++ embassy-stm32/src/lptim/mod.rs | 22 +-- embassy-stm32/src/lptim/pwm.rs | 107 ++++++++---- .../src/lptim/timer/channel_direction.rs | 18 ++ .../src/lptim/{timer.rs => timer/mod.rs} | 156 +++++------------- embassy-stm32/src/lptim/timer/prescaler.rs | 90 ++++++++++ 7 files changed, 245 insertions(+), 167 deletions(-) create mode 100644 embassy-stm32/src/lptim/channel.rs create mode 100644 embassy-stm32/src/lptim/timer/channel_direction.rs rename embassy-stm32/src/lptim/{timer.rs => timer/mod.rs} (53%) create mode 100644 embassy-stm32/src/lptim/timer/prescaler.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 33985a3f4..511eb6d37 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1017,6 +1017,7 @@ fn main() { (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), (("lptim", "CH2"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "OUT"), quote!(crate::lptim::OutputPin)), (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), (("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)), diff --git a/embassy-stm32/src/lptim/channel.rs b/embassy-stm32/src/lptim/channel.rs new file mode 100644 index 000000000..17fc2fb86 --- /dev/null +++ b/embassy-stm32/src/lptim/channel.rs @@ -0,0 +1,18 @@ +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { + /// Channel 1. + Ch1, + /// Channel 2. + Ch2, +} + +impl Channel { + /// Get the channel index (0..1) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, + } + } +} diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index dc5f1d5bb..9c10efa43 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -6,24 +6,12 @@ pub mod timer; use crate::rcc::RccPeripheral; /// Timer channel. -#[derive(Clone, Copy)] -pub enum Channel { - /// Channel 1. - Ch1, - /// Channel 2. - Ch2, -} - -impl Channel { - /// Get the channel index (0..1) - pub fn index(&self) -> usize { - match self { - Channel::Ch1 => 0, - Channel::Ch2 => 1, - } - } -} +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel::Channel; +pin_trait!(OutputPin, Instance); pin_trait!(Channel1Pin, Instance); pin_trait!(Channel2Pin, Instance); diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs index 24725f625..586148848 100644 --- a/embassy-stm32/src/lptim/pwm.rs +++ b/embassy-stm32/src/lptim/pwm.rs @@ -4,12 +4,18 @@ use core::marker::PhantomData; use embassy_hal_internal::{into_ref, PeripheralRef}; -use super::timer::{ChannelDirection, Timer}; -use super::{Channel, Channel1Pin, Channel2Pin, Instance}; +use super::timer::Timer; +use super::Instance; +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +use super::OutputPin; +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::{channel::Channel, timer::ChannelDirection, Channel1Pin, Channel2Pin}; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; use crate::time::Hertz; use crate::Peripheral; +/// Output marker type. +pub enum Output {} /// Channel 1 marker type. pub enum Ch1 {} /// Channel 2 marker type. @@ -45,14 +51,44 @@ macro_rules! channel_impl { }; } +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +channel_impl!(new, Output, OutputPin); +#[cfg(any(lptim_v2a, lptim_v2b))] channel_impl!(new_ch1, Ch1, Channel1Pin); +#[cfg(any(lptim_v2a, lptim_v2b))] channel_impl!(new_ch2, Ch2, Channel2Pin); /// PWM driver. pub struct Pwm<'d, T: Instance> { - inner: Timer<'d, T>, // _inner: PeripheralRef<'d, T>, + inner: Timer<'d, T>, } +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new(tim: impl Peripheral

+ 'd, _output_pin: PwmPin<'d, T, Output>, freq: Hertz) -> Self { + Self::new_inner(tim, freq) + } + + /// Set the duty. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(duty) + } + + /// Get the duty. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self) -> u16 { + self.inner.get_compare_value() + } + + fn post_init(&mut self) {} +} + +#[cfg(any(lptim_v2a, lptim_v2b))] impl<'d, T: Instance> Pwm<'d, T> { /// Create a new PWM driver. pub fn new( @@ -61,19 +97,7 @@ impl<'d, T: Instance> Pwm<'d, T> { _ch2_pin: Option>, freq: Hertz, ) -> Self { - let mut this = Self { inner: Timer::new(tim) }; - - this.inner.enable(); - this.set_frequency(freq); - - #[cfg(any(lptim_v2a, lptim_v2b))] - [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { - this.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); - }); - - this.inner.continuous_mode_start(); - - this + Self::new_inner(tim, freq) } /// Enable the given channel. @@ -91,21 +115,6 @@ impl<'d, T: Instance> Pwm<'d, T> { self.inner.get_channel_enable_state(channel) } - /// Set PWM frequency. - /// - /// Note: when you call this, the max duty value changes, so you will have to - /// call `set_duty` on all channels with the duty calculated based on the new max duty. - pub fn set_frequency(&mut self, frequency: Hertz) { - self.inner.set_frequency(frequency); - } - - /// Get max duty value. - /// - /// This value depends on the configured frequency and the timer's clock rate from RCC. - pub fn get_max_duty(&self) -> u16 { - self.inner.get_max_compare_value() + 1 - } - /// Set the duty for a given channel. /// /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. @@ -120,4 +129,40 @@ impl<'d, T: Instance> Pwm<'d, T> { pub fn get_duty(&self, channel: Channel) -> u16 { self.inner.get_compare_value(channel) } + + fn post_init(&mut self) { + [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { + self.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); + }); + } +} + +impl<'d, T: Instance> Pwm<'d, T> { + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.enable(); + this.set_frequency(freq); + + this.post_init(); + + this.inner.continuous_mode_start(); + + this + } + + /// Set PWM frequency. + /// + /// Note: when you call this, the max duty value changes, so you will have to + /// call `set_duty` on all channels with the duty calculated based on the new max duty. + pub fn set_frequency(&mut self, frequency: Hertz) { + self.inner.set_frequency(frequency); + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn get_max_duty(&self) -> u16 { + self.inner.get_max_compare_value() + 1 + } } diff --git a/embassy-stm32/src/lptim/timer/channel_direction.rs b/embassy-stm32/src/lptim/timer/channel_direction.rs new file mode 100644 index 000000000..a38df63cd --- /dev/null +++ b/embassy-stm32/src/lptim/timer/channel_direction.rs @@ -0,0 +1,18 @@ +use crate::pac::lptim::vals; + +/// Direction of a low-power timer channel +pub enum ChannelDirection { + /// Use channel as a PWM output + OutputPwm, + /// Use channel as an input capture + InputCapture, +} + +impl From for vals::Ccsel { + fn from(direction: ChannelDirection) -> Self { + match direction { + ChannelDirection::OutputPwm => vals::Ccsel::OUTPUTCOMPARE, + ChannelDirection::InputCapture => vals::Ccsel::INPUTCAPTURE, + } + } +} diff --git a/embassy-stm32/src/lptim/timer.rs b/embassy-stm32/src/lptim/timer/mod.rs similarity index 53% rename from embassy-stm32/src/lptim/timer.rs rename to embassy-stm32/src/lptim/timer/mod.rs index b354c1f61..e62fcab49 100644 --- a/embassy-stm32/src/lptim/timer.rs +++ b/embassy-stm32/src/lptim/timer/mod.rs @@ -1,118 +1,20 @@ //! Low-level timer driver. +mod prescaler; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; -use super::{Channel, Instance}; -use crate::pac::lptim::vals; +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::channel::Channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel_direction; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel_direction::ChannelDirection; +use prescaler::Prescaler; + +use super::Instance; use crate::rcc; use crate::time::Hertz; -/// Direction of a low-power timer channel -#[cfg(any(lptim_v2a, lptim_v2b))] -pub enum ChannelDirection { - /// Use channel as a PWM output - OutputPwm, - /// Use channel as an input capture - InputCapture, -} - -#[cfg(any(lptim_v2a, lptim_v2b))] -impl From for vals::Ccsel { - fn from(direction: ChannelDirection) -> Self { - match direction { - ChannelDirection::OutputPwm => vals::Ccsel::OUTPUTCOMPARE, - ChannelDirection::InputCapture => vals::Ccsel::INPUTCAPTURE, - } - } -} - -enum Prescaler { - Div1, - Div2, - Div4, - Div8, - Div16, - Div32, - Div64, - Div128, -} - -impl From<&Prescaler> for vals::Presc { - fn from(prescaler: &Prescaler) -> Self { - match prescaler { - Prescaler::Div1 => vals::Presc::DIV1, - Prescaler::Div2 => vals::Presc::DIV2, - Prescaler::Div4 => vals::Presc::DIV4, - Prescaler::Div8 => vals::Presc::DIV8, - Prescaler::Div16 => vals::Presc::DIV16, - Prescaler::Div32 => vals::Presc::DIV32, - Prescaler::Div64 => vals::Presc::DIV64, - Prescaler::Div128 => vals::Presc::DIV128, - } - } -} - -impl From for Prescaler { - fn from(prescaler: vals::Presc) -> Self { - match prescaler { - vals::Presc::DIV1 => Prescaler::Div1, - vals::Presc::DIV2 => Prescaler::Div2, - vals::Presc::DIV4 => Prescaler::Div4, - vals::Presc::DIV8 => Prescaler::Div8, - vals::Presc::DIV16 => Prescaler::Div16, - vals::Presc::DIV32 => Prescaler::Div32, - vals::Presc::DIV64 => Prescaler::Div64, - vals::Presc::DIV128 => Prescaler::Div128, - } - } -} - -impl From<&Prescaler> for u32 { - fn from(prescaler: &Prescaler) -> Self { - match prescaler { - Prescaler::Div1 => 1, - Prescaler::Div2 => 2, - Prescaler::Div4 => 4, - Prescaler::Div8 => 8, - Prescaler::Div16 => 16, - Prescaler::Div32 => 32, - Prescaler::Div64 => 64, - Prescaler::Div128 => 128, - } - } -} - -impl From for Prescaler { - fn from(prescaler: u32) -> Self { - match prescaler { - 1 => Prescaler::Div1, - 2 => Prescaler::Div2, - 4 => Prescaler::Div4, - 8 => Prescaler::Div8, - 16 => Prescaler::Div16, - 32 => Prescaler::Div32, - 64 => Prescaler::Div64, - 128 => Prescaler::Div128, - _ => unreachable!(), - } - } -} - -impl Prescaler { - pub fn from_ticks(ticks: u32) -> Self { - // We need to scale down to a 16-bit range - (ticks >> 16).next_power_of_two().into() - } - - pub fn scale_down(&self, ticks: u32) -> u16 { - (ticks / u32::from(self)).try_into().unwrap() - } - - pub fn scale_up(&self, ticks: u16) -> u32 { - u32::from(self) * ticks as u32 - } -} - /// Low-level timer driver. pub struct Timer<'d, T: Instance> { _tim: PeripheralRef<'d, T>, @@ -148,14 +50,6 @@ impl<'d, T: Instance> Timer<'d, T> { T::regs().cr().modify(|w| w.set_cntstrt(true)); } - /// Set channel direction. - #[cfg(any(lptim_v2a, lptim_v2b))] - pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { - T::regs() - .ccmr(0) - .modify(|w| w.set_ccsel(channel.index(), direction.into())); - } - /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. pub fn set_frequency(&self, frequency: Hertz) { let f = frequency.0; @@ -186,6 +80,14 @@ impl<'d, T: Instance> Timer<'d, T> { T::frequency() } + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u16 { + T::regs().arr().read().arr() + } +} + +#[cfg(any(lptim_v2a, lptim_v2b))] +impl<'d, T: Instance> Timer<'d, T> { /// Enable/disable a channel. pub fn enable_channel(&self, channel: Channel, enable: bool) { T::regs().ccmr(0).modify(|w| { @@ -208,8 +110,24 @@ impl<'d, T: Instance> Timer<'d, T> { T::regs().ccr(channel.index()).read().ccr() } - /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. - pub fn get_max_compare_value(&self) -> u16 { - T::regs().arr().read().arr() + /// Set channel direction. + #[cfg(any(lptim_v2a, lptim_v2b))] + pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { + T::regs() + .ccmr(0) + .modify(|w| w.set_ccsel(channel.index(), direction.into())); + } +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Timer<'d, T> { + /// Set compare value for a channel. + pub fn set_compare_value(&self, value: u16) { + T::regs().cmp().modify(|w| w.set_cmp(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self) -> u16 { + T::regs().cmp().read().cmp() } } diff --git a/embassy-stm32/src/lptim/timer/prescaler.rs b/embassy-stm32/src/lptim/timer/prescaler.rs new file mode 100644 index 000000000..5d2326faf --- /dev/null +++ b/embassy-stm32/src/lptim/timer/prescaler.rs @@ -0,0 +1,90 @@ +//! Low-level timer driver. + +use crate::pac::lptim::vals; + +pub enum Prescaler { + Div1, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl From<&Prescaler> for vals::Presc { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => vals::Presc::DIV1, + Prescaler::Div2 => vals::Presc::DIV2, + Prescaler::Div4 => vals::Presc::DIV4, + Prescaler::Div8 => vals::Presc::DIV8, + Prescaler::Div16 => vals::Presc::DIV16, + Prescaler::Div32 => vals::Presc::DIV32, + Prescaler::Div64 => vals::Presc::DIV64, + Prescaler::Div128 => vals::Presc::DIV128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: vals::Presc) -> Self { + match prescaler { + vals::Presc::DIV1 => Prescaler::Div1, + vals::Presc::DIV2 => Prescaler::Div2, + vals::Presc::DIV4 => Prescaler::Div4, + vals::Presc::DIV8 => Prescaler::Div8, + vals::Presc::DIV16 => Prescaler::Div16, + vals::Presc::DIV32 => Prescaler::Div32, + vals::Presc::DIV64 => Prescaler::Div64, + vals::Presc::DIV128 => Prescaler::Div128, + } + } +} + +impl From<&Prescaler> for u32 { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => 1, + Prescaler::Div2 => 2, + Prescaler::Div4 => 4, + Prescaler::Div8 => 8, + Prescaler::Div16 => 16, + Prescaler::Div32 => 32, + Prescaler::Div64 => 64, + Prescaler::Div128 => 128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: u32) -> Self { + match prescaler { + 1 => Prescaler::Div1, + 2 => Prescaler::Div2, + 4 => Prescaler::Div4, + 8 => Prescaler::Div8, + 16 => Prescaler::Div16, + 32 => Prescaler::Div32, + 64 => Prescaler::Div64, + 128 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn from_ticks(ticks: u32) -> Self { + // We need to scale down to a 16-bit range + (ticks >> 16).next_power_of_two().into() + } + + pub fn scale_down(&self, ticks: u32) -> u16 { + (ticks / u32::from(self)).try_into().unwrap() + } + + pub fn scale_up(&self, ticks: u16) -> u32 { + u32::from(self) * ticks as u32 + } +} From ac319970b82e3e613c35d45a11f31423204115e0 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 5 Sep 2024 16:30:45 -0400 Subject: [PATCH 074/127] stm32: Work around LPTIM4 --- embassy-stm32/src/lptim/mod.rs | 25 ++++++++++++++++++------- embassy-stm32/src/lptim/pwm.rs | 4 ++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index 9c10efa43..05ed4de44 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -11,17 +11,23 @@ mod channel; #[cfg(any(lptim_v2a, lptim_v2b))] pub use channel::Channel; -pin_trait!(OutputPin, Instance); -pin_trait!(Channel1Pin, Instance); -pin_trait!(Channel2Pin, Instance); +pin_trait!(OutputPin, BasicInstance); +pin_trait!(Channel1Pin, BasicInstance); +pin_trait!(Channel2Pin, BasicInstance); pub(crate) trait SealedInstance: RccPeripheral { fn regs() -> crate::pac::lptim::Lptim; } +pub(crate) trait SealedBasicInstance: RccPeripheral {} + +/// LPTIM basic instance trait. +#[allow(private_bounds)] +pub trait BasicInstance: SealedBasicInstance + 'static {} /// LPTIM instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static {} +pub trait Instance: BasicInstance + SealedInstance + 'static {} + foreach_interrupt! { ($inst:ident, lptim, LPTIM, UP, $irq:ident) => { impl SealedInstance for crate::peripherals::$inst { @@ -29,9 +35,14 @@ foreach_interrupt! { crate::pac::$inst } } - - impl Instance for crate::peripherals::$inst { - + impl SealedBasicInstance for crate::peripherals::$inst { } + impl BasicInstance for crate::peripherals::$inst {} + impl Instance for crate::peripherals::$inst {} + }; + ($inst:ident, lptim, LPTIM_BASIC, UP, $irq:ident) => { + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} }; } diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs index 586148848..1f43eb6ee 100644 --- a/embassy-stm32/src/lptim/pwm.rs +++ b/embassy-stm32/src/lptim/pwm.rs @@ -5,11 +5,11 @@ use core::marker::PhantomData; use embassy_hal_internal::{into_ref, PeripheralRef}; use super::timer::Timer; -use super::Instance; #[cfg(not(any(lptim_v2a, lptim_v2b)))] use super::OutputPin; #[cfg(any(lptim_v2a, lptim_v2b))] use super::{channel::Channel, timer::ChannelDirection, Channel1Pin, Channel2Pin}; +use super::{BasicInstance, Instance}; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; use crate::time::Hertz; use crate::Peripheral; @@ -31,7 +31,7 @@ pub struct PwmPin<'d, T, C> { macro_rules! channel_impl { ($new_chx:ident, $channel:ident, $pin_trait:ident) => { - impl<'d, T: Instance> PwmPin<'d, T, $channel> { + impl<'d, T: BasicInstance> PwmPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { into_ref!(pin); From 69208daf6dba8ef5285e309219991dd63c38506e Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 5 Sep 2024 17:42:09 -0400 Subject: [PATCH 075/127] stm32: Fixing stupid typo (thanks @dirbaio) --- embassy-stm32/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 511eb6d37..19cf193d9 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1016,7 +1016,7 @@ fn main() { (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), - (("lptim", "CH2"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "CH2"), quote!(crate::lptim::Channel2Pin)), (("lptim", "OUT"), quote!(crate::lptim::OutputPin)), (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), From d69f68b8c006dc785652a74fbe730583900d1923 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 6 Sep 2024 20:15:45 -0400 Subject: [PATCH 076/127] stm32: Use a GLOBAL interrupt for lptim --- embassy-stm32/src/lptim/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index 05ed4de44..1649cc5b4 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -29,7 +29,7 @@ pub trait BasicInstance: SealedBasicInstance + 'static {} pub trait Instance: BasicInstance + SealedInstance + 'static {} foreach_interrupt! { - ($inst:ident, lptim, LPTIM, UP, $irq:ident) => { + ($inst:ident, lptim, LPTIM, GLOBAL, $irq:ident) => { impl SealedInstance for crate::peripherals::$inst { fn regs() -> crate::pac::lptim::Lptim { crate::pac::$inst @@ -40,7 +40,7 @@ foreach_interrupt! { impl BasicInstance for crate::peripherals::$inst {} impl Instance for crate::peripherals::$inst {} }; - ($inst:ident, lptim, LPTIM_BASIC, UP, $irq:ident) => { + ($inst:ident, lptim, LPTIM_BASIC, GLOBAL, $irq:ident) => { impl SealedBasicInstance for crate::peripherals::$inst { } impl BasicInstance for crate::peripherals::$inst {} From c739091085eafeb01703626284428905d75aa98f Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 11 Sep 2024 01:00:23 +0200 Subject: [PATCH 077/127] Update stm32-metapac. --- embassy-stm32/Cargo.toml | 4 ++-- embassy-stm32/src/adc/mod.rs | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 9a6a5908e..3c6484c96 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,7 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-5ef354f3e49f790e47f5c818f243459742c9b83b" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364" } vcell = "0.1.3" nb = "1.0.0" @@ -99,7 +99,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-5ef354f3e49f790e47f5c818f243459742c9b83b", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364", default-features = false, features = ["metadata"] } [features] default = ["rt"] diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 2ac0c0083..4ab82c1d9 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -4,7 +4,7 @@ #![allow(missing_docs)] // TODO #![cfg_attr(adc_f3_v2, allow(unused))] -#[cfg(not(adc_f3_v2))] +#[cfg(not(any(adc_f3_v2, adc_u5)))] #[cfg_attr(adc_f1, path = "f1.rs")] #[cfg_attr(adc_f3, path = "f3.rs")] #[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")] @@ -19,14 +19,16 @@ mod _version; use core::marker::PhantomData; #[allow(unused)] -#[cfg(not(adc_f3_v2))] +#[cfg(not(any(adc_f3_v2, adc_u5)))] pub use _version::*; #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] use embassy_sync::waitqueue::AtomicWaker; +#[cfg(not(any(adc_u5)))] pub use crate::pac::adc::vals; -#[cfg(not(any(adc_f1, adc_f3_v2)))] +#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))] pub use crate::pac::adc::vals::Res as Resolution; +#[cfg(not(any(adc_u5)))] pub use crate::pac::adc::vals::SampleTime; use crate::peripherals; @@ -36,7 +38,7 @@ dma_trait!(RxDma, Instance); pub struct Adc<'d, T: Instance> { #[allow(unused)] adc: crate::PeripheralRef<'d, T>, - #[cfg(not(any(adc_f3_v2, adc_f3_v1_1)))] + #[cfg(not(any(adc_f3_v2, adc_f3_v1_1, adc_u5)))] sample_time: SampleTime, } @@ -57,7 +59,7 @@ impl State { trait SealedInstance { #[allow(unused)] fn regs() -> crate::pac::adc::Adc; - #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))] + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))] #[allow(unused)] fn common_regs() -> crate::pac::adccommon::AdcCommon; #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] @@ -163,7 +165,7 @@ foreach_adc!( crate::pac::$inst } - #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))] + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))] fn common_regs() -> crate::pac::adccommon::AdcCommon { return crate::pac::$common_inst } @@ -200,7 +202,7 @@ macro_rules! impl_adc_pin { /// Get the maximum reading value for this resolution. /// /// This is `2**n - 1`. -#[cfg(not(any(adc_f1, adc_f3_v2)))] +#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))] pub const fn resolution_to_max_count(res: Resolution) -> u32 { match res { #[cfg(adc_v4)] From d193c9ef44a61c9de381044fd2968a95ce2ffc75 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 13 Aug 2024 17:45:48 +0200 Subject: [PATCH 078/127] time-driver: clarify docs for set_alarm. --- embassy-time-driver/src/lib.rs | 52 ++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/embassy-time-driver/src/lib.rs b/embassy-time-driver/src/lib.rs index aab2f626e..8000a9dcb 100644 --- a/embassy-time-driver/src/lib.rs +++ b/embassy-time-driver/src/lib.rs @@ -113,24 +113,52 @@ pub trait Driver: Send + Sync + 'static { /// It is UB to make the alarm fire before setting a callback. unsafe fn allocate_alarm(&self) -> Option; - /// Sets the callback function to be called when the alarm triggers. + /// Set the callback function to be called when the alarm triggers. /// The callback may be called from any context (interrupt or thread mode). fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm - /// timestamp, the provided callback function will be called. + /// Set an alarm at the given timestamp. /// - /// The `Driver` implementation should guarantee that the alarm callback is never called synchronously from `set_alarm`. - /// Rather - if `timestamp` is already in the past - `false` should be returned and alarm should not be set, - /// or alternatively, the driver should return `true` and arrange to call the alarm callback as soon as possible, but not synchronously. - /// There is a rare third possibility that the alarm was barely in the future, and by the time it was enabled, it had slipped into the - /// past. This is can be detected by double-checking that the alarm is still in the future after enabling it; if it isn't, `false` - /// should also be returned to indicate that the callback may have been called already by the alarm, but it is not guaranteed, so the - /// caller should also call the callback, just like in the more common `false` case. (Note: This requires idempotency of the callback.) + /// ## Behavior /// - /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. + /// If `timestamp` is in the future, `set_alarm` schedules calling the callback function + /// at that time, and returns `true`. /// - /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. + /// If `timestamp` is in the past, `set_alarm` has two allowed behaviors. Implementations can pick whether to: + /// + /// - Schedule calling the callback function "immediately", as if the requested timestamp was "now+epsilon" and return `true`, or + /// - Not schedule the callback, and return `false`. + /// + /// Callers must ensure to behave correctly with either behavior. + /// + /// When callback is called, it is guaranteed that `now()` will return a value greater than or equal to `timestamp`. + /// + /// ## Reentrancy + /// + /// Calling the callback from `set_alarm` synchronously is not allowed. If the implementation chooses the first option above, + /// it must still call the callback from another context (i.e. an interrupt handler or background thread), it's not allowed + /// to call it synchronously in the context `set_alarm` is running. + /// + /// The reason for the above is callers are explicitly permitted to do both of: + /// - Lock a mutex in the alarm callback. + /// - Call `set_alarm` while having that mutex locked. + /// + /// If `set_alarm` called the callback synchronously, it'd cause a deadlock or panic because it'd cause the + /// mutex to be locked twice reentrantly in the same context. + /// + /// ## Overwriting alarms + /// + /// Only one alarm can be active at a time for each `AlarmHandle`. This overwrites any previously-set alarm if any. + /// + /// ## Unsetting the alarm + /// + /// There is no `unset_alarm` API. Instead, callers can call `set_alarm` with `timestamp` set to `u64::MAX`. + /// + /// This allows for more efficient implementations, since they don't need to distinguish between the "alarm set" and + /// "alarm not set" cases, thanks to the fact "Alarm set for u64::MAX" is effectively equivalent for "alarm not set". + /// + /// This means implementations need to be careful to avoid timestamp overflows. The recommendation is to make `timestamp` + /// be in the same units as hardware ticks to avoid any conversions, which makes avoiding overflow easier. fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool; } From 833537231e763c31eeb29fe7aad005a08477189d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCha=20=C3=9Cn=C3=BCvar?= <87157627+phycrax@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:50:46 +0800 Subject: [PATCH 079/127] add link to rustybits zero to async video in resources section --- docs/pages/overview.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/overview.adoc b/docs/pages/overview.adoc index 7d59d5521..2ebc85f6d 100644 --- a/docs/pages/overview.adoc +++ b/docs/pages/overview.adoc @@ -77,3 +77,7 @@ For more reading material on async Rust and Embassy: * link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy] * link:https://dev.to/apollolabsbin/series/20707[Tutorials] * link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy] + +Videos: + +* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] \ No newline at end of file From 29932c295c44cea1a2ab5ad94c5d0bd16c07243d Mon Sep 17 00:00:00 2001 From: Oleksandr Babak Date: Wed, 11 Sep 2024 10:42:46 +0200 Subject: [PATCH 080/127] fix: `select_slice` is unsound. fixes #3320 --- embassy-futures/src/select.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 97a81a86d..57f0cb41f 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -237,7 +237,7 @@ impl Future for SelectArray { #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct SelectSlice<'a, Fut> { - inner: &'a mut [Fut], + inner: Pin<&'a mut [Fut]>, } /// Creates a new future which will select over a slice of futures. @@ -247,31 +247,26 @@ pub struct SelectSlice<'a, Fut> { /// future that was ready. /// /// If the slice is empty, the resulting future will be Pending forever. -pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> { +pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> { SelectSlice { inner: slice } } impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { type Output = (Fut::Output, usize); - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, - // its elements also cannot move. Therefore it is safe to access `inner` and pin - // references to the contained futures. - let item = unsafe { - self.get_unchecked_mut() - .inner - .iter_mut() - .enumerate() - .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { - Poll::Pending => None, - Poll::Ready(e) => Some((i, e)), - }) - }; - - match item { - Some((idx, res)) => Poll::Ready((res, idx)), - None => Poll::Pending, + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: refer to + // https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2 + #[inline(always)] + fn pin_iter(slice: Pin<&mut [T]>) -> impl Iterator> { + unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) } } + for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() { + if let Poll::Ready(res) = fut.poll(cx) { + return Poll::Ready((res, i)); + } + } + + Poll::Pending } } From 3d6a270f30c45eaf394c8eb8bf182dd1a7ec2d7b Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Wed, 11 Sep 2024 19:44:11 -0400 Subject: [PATCH 081/127] rp: Fix indexing for pins >31 on rp235xb (#3330) * Fix indexing for pins >31 on rp235xb * fixup knowing that 1<<7 is 128 not 64 --- embassy-rp/src/gpio.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index d0bb7e574..31397172c 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -846,12 +846,12 @@ pub(crate) trait SealedPin: Sized { #[inline] fn _pin(&self) -> u8 { - self.pin_bank() & 0x1f + self.pin_bank() & 0x7f } #[inline] fn _bank(&self) -> Bank { - match self.pin_bank() >> 5 { + match self.pin_bank() >> 7 { #[cfg(feature = "qspi-as-gpio")] 1 => Bank::Qspi, _ => Bank::Bank0, @@ -880,15 +880,27 @@ pub(crate) trait SealedPin: Sized { } fn sio_out(&self) -> pac::sio::Gpio { - SIO.gpio_out(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_out(self._bank() as _) + } else { + SIO.gpio_out((self._pin() / 32) as _) + } } fn sio_oe(&self) -> pac::sio::Gpio { - SIO.gpio_oe(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_oe(self._bank() as _) + } else { + SIO.gpio_oe((self._pin() / 32) as _) + } } fn sio_in(&self) -> Reg { - SIO.gpio_in(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_in(self._bank() as _) + } else { + SIO.gpio_in((self._pin() / 32) as _) + } } fn int_proc(&self) -> pac::io::Int { @@ -953,7 +965,7 @@ macro_rules! impl_pin { impl SealedPin for peripherals::$name { #[inline] fn pin_bank(&self) -> u8 { - ($bank as u8) * 32 + $pin_num + ($bank as u8) * 128 + $pin_num } } From eeda57a4245019ec1acd13afd9d826679d5ebb5e Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 12 Sep 2024 11:37:17 -0400 Subject: [PATCH 082/127] rp2350 pio pin fixes Disable pad isolation on any used pin. Use GPIOBASE and offset pin bases if all pins are > 16, panic if some pins are < 16 and some are > 31 --- embassy-rp/src/pio/mod.rs | 120 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 68b1d6849..0d489763c 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -655,6 +655,10 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// Set pin used to signal jump. pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { + #[cfg(feature = "_rp235x")] + pin.pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); self.exec.jmp_pin = pin.pin(); } @@ -664,6 +668,12 @@ impl<'d, PIO: Instance> Config<'d, PIO> { pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { assert!(pins.len() <= 5); assert_consecutive(pins); + #[cfg(feature = "_rp235x")] + for pin in pins { + pin.pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }) + } self.pins.set_base = pins.first().map_or(0, |p| p.pin()); self.pins.set_count = pins.len() as u8; } @@ -673,6 +683,12 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// effective. pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { assert_consecutive(pins); + #[cfg(feature = "_rp235x")] + for pin in pins { + pin.pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }) + } self.pins.out_base = pins.first().map_or(0, |p| p.pin()); self.pins.out_count = pins.len() as u8; } @@ -682,6 +698,12 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// effective. pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { assert_consecutive(pins); + #[cfg(feature = "_rp235x")] + for pin in pins { + pin.pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }) + } self.pins.in_base = pins.first().map_or(0, |p| p.pin()); self.in_count = pins.len() as u8; } @@ -731,6 +753,8 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_autopull(config.shift_out.auto_fill); w.set_autopush(config.shift_in.auto_fill); }); + + #[cfg(feature = "rp2040")] sm.pinctrl().write(|w| { w.set_sideset_count(config.pins.sideset_count); w.set_set_count(config.pins.set_count); @@ -740,6 +764,102 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_set_base(config.pins.set_base); w.set_out_base(config.pins.out_base); }); + + #[cfg(feature = "_rp235x")] + { + let shift_gpio_base = { + match ( + if config.in_count > 0 { + Some(config.pins.in_base) + } else { + None + }, + if config.pins.sideset_count > 0 { + Some(config.pins.sideset_base) + } else { + None + }, + if config.pins.set_count > 0 { + Some(config.pins.set_base) + } else { + None + }, + if config.pins.out_count > 0 { + Some(config.pins.out_base) + } else { + None + }, + ) { + (None, None, None, None) => false, + + (Some(0..31), None, None, None) => false, + (None, Some(0..31), None, None) => false, + (None, None, Some(0..31), None) => false, + (None, None, None, Some(0..31)) => false, + + (Some(0..31), Some(0..31), None, None) => false, + (None, Some(0..31), Some(0..31), None) => false, + (None, None, Some(0..31), Some(0..31)) => false, + (Some(0..31), None, None, Some(0..31)) => false, + + (None, Some(0..31), Some(0..31), Some(0..31)) => false, + (Some(0..31), None, Some(0..31), Some(0..31)) => false, + (Some(0..31), Some(0..31), None, Some(0..31)) => false, + (Some(0..31), Some(0..31), Some(0..31), None) => false, + + (Some(0..31), Some(0..31), Some(0..31), Some(0..31)) => false, + + (Some(16..48), None, None, None) => true, + (None, Some(16..48), None, None) => true, + (None, None, Some(16..48), None) => true, + (None, None, None, Some(16..48)) => true, + + (Some(16..48), Some(16..48), None, None) => true, + (None, Some(16..48), Some(16..48), None) => true, + (None, None, Some(16..48), Some(16..48)) => true, + (Some(16..48), None, None, Some(16..48)) => true, + + (None, Some(16..48), Some(16..48), Some(16..48)) => true, + (Some(16..48), None, Some(16..48), Some(16..48)) => true, + (Some(16..48), Some(16..48), None, Some(16..48)) => true, + (Some(16..48), Some(16..48), Some(16..48), None) => true, + + (Some(16..48), Some(16..48), Some(16..48), Some(16..48)) => true, + + (i, side, set, out) => panic!( + "All pins must either be < 31 or >16, in:{}, side:{}, set:{}, out:{}", + i, side, set, out + ), + } + }; + + info!("shift: {}", shift_gpio_base); + + if shift_gpio_base { + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base - 16); + w.set_sideset_base(config.pins.sideset_base - 16); + w.set_set_base(config.pins.set_base - 16); + w.set_out_base(config.pins.out_base - 16); + }); + } else { + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base); + w.set_sideset_base(config.pins.sideset_base); + w.set_set_base(config.pins.set_base); + w.set_out_base(config.pins.out_base); + }); + } + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift_gpio_base)); + } + if let Some(origin) = config.origin { unsafe { instr::exec_jmp(self, origin) } } From 823a82adb7206435794c769be699e1b972d6f966 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 12 Sep 2024 11:43:15 -0400 Subject: [PATCH 083/127] Fixup formatting to work with log --- embassy-rp/src/pio/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 0d489763c..ec29a690c 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -827,7 +827,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { (Some(16..48), Some(16..48), Some(16..48), Some(16..48)) => true, (i, side, set, out) => panic!( - "All pins must either be < 31 or >16, in:{}, side:{}, set:{}, out:{}", + "All pins must either be < 31 or >16, in:{:?}, side:{:?}, set:{:?}, out:{:?}", i, side, set, out ), } From e359b24121d9747ea8260d8887829b6a97c312f1 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 12 Sep 2024 11:50:36 -0400 Subject: [PATCH 084/127] remove debug print --- embassy-rp/src/pio/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index ec29a690c..2b1ed554e 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -833,8 +833,6 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { } }; - info!("shift: {}", shift_gpio_base); - if shift_gpio_base { sm.pinctrl().write(|w| { w.set_sideset_count(config.pins.sideset_count); From ed6cbc7a3a1082b2063162cbf30fb6f9979d81ad Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Sun, 15 Sep 2024 20:09:18 -0400 Subject: [PATCH 085/127] Fix upper bound of case where pins should be <=31 --- embassy-rp/src/pio/mod.rs | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 2b1ed554e..e8e411a25 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -792,42 +792,42 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { ) { (None, None, None, None) => false, - (Some(0..31), None, None, None) => false, - (None, Some(0..31), None, None) => false, - (None, None, Some(0..31), None) => false, - (None, None, None, Some(0..31)) => false, + (Some(..32), None, None, None) => false, + (None, Some(..32), None, None) => false, + (None, None, Some(..32), None) => false, + (None, None, None, Some(..32)) => false, - (Some(0..31), Some(0..31), None, None) => false, - (None, Some(0..31), Some(0..31), None) => false, - (None, None, Some(0..31), Some(0..31)) => false, - (Some(0..31), None, None, Some(0..31)) => false, + (Some(..32), Some(..32), None, None) => false, + (None, Some(..32), Some(..32), None) => false, + (None, None, Some(..32), Some(..32)) => false, + (Some(..32), None, None, Some(..32)) => false, - (None, Some(0..31), Some(0..31), Some(0..31)) => false, - (Some(0..31), None, Some(0..31), Some(0..31)) => false, - (Some(0..31), Some(0..31), None, Some(0..31)) => false, - (Some(0..31), Some(0..31), Some(0..31), None) => false, + (None, Some(..32), Some(..32), Some(..32)) => false, + (Some(..32), None, Some(..32), Some(..32)) => false, + (Some(..32), Some(..32), None, Some(..32)) => false, + (Some(..32), Some(..32), Some(..32), None) => false, - (Some(0..31), Some(0..31), Some(0..31), Some(0..31)) => false, + (Some(..32), Some(..32), Some(..32), Some(..32)) => false, - (Some(16..48), None, None, None) => true, - (None, Some(16..48), None, None) => true, - (None, None, Some(16..48), None) => true, - (None, None, None, Some(16..48)) => true, + (Some(16..), None, None, None) => true, + (None, Some(16..), None, None) => true, + (None, None, Some(16..), None) => true, + (None, None, None, Some(16..)) => true, - (Some(16..48), Some(16..48), None, None) => true, - (None, Some(16..48), Some(16..48), None) => true, - (None, None, Some(16..48), Some(16..48)) => true, - (Some(16..48), None, None, Some(16..48)) => true, + (Some(16..), Some(16..), None, None) => true, + (None, Some(16..), Some(16..), None) => true, + (None, None, Some(16..), Some(16..)) => true, + (Some(16..), None, None, Some(16..)) => true, - (None, Some(16..48), Some(16..48), Some(16..48)) => true, - (Some(16..48), None, Some(16..48), Some(16..48)) => true, - (Some(16..48), Some(16..48), None, Some(16..48)) => true, - (Some(16..48), Some(16..48), Some(16..48), None) => true, + (None, Some(16..), Some(16..), Some(16..)) => true, + (Some(16..), None, Some(16..), Some(16..)) => true, + (Some(16..), Some(16..), None, Some(16..)) => true, + (Some(16..), Some(16..), Some(16..), None) => true, - (Some(16..48), Some(16..48), Some(16..48), Some(16..48)) => true, + (Some(16..), Some(16..), Some(16..), Some(16..)) => true, (i, side, set, out) => panic!( - "All pins must either be < 31 or >16, in:{:?}, side:{:?}, set:{:?}, out:{:?}", + "All pins must either be <=31 or >=16, in:{:?}, side:{:?}, set:{:?}, out:{:?}", i, side, set, out ), } From 55c3da5a4f48ea6c66371484c83d6298cde3befe Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Sun, 15 Sep 2024 20:48:54 -0400 Subject: [PATCH 086/127] Properly drop pins >30 --- embassy-rp/src/gpio.rs | 4 ++-- embassy-rp/src/pio/mod.rs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 31397172c..520043b07 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -16,9 +16,9 @@ use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; const NEW_AW: AtomicWaker = AtomicWaker::new(); #[cfg(any(feature = "rp2040", feature = "rp235xa"))] -const BANK0_PIN_COUNT: usize = 30; +pub(crate) const BANK0_PIN_COUNT: usize = 30; #[cfg(feature = "rp235xb")] -const BANK0_PIN_COUNT: usize = 48; +pub(crate) const BANK0_PIN_COUNT: usize = 48; static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [NEW_AW; BANK0_PIN_COUNT]; #[cfg(feature = "qspi-as-gpio")] diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index e8e411a25..fc940b045 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -5,7 +5,7 @@ use core::pin::Pin as FuturePin; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::{Context, Poll}; -use atomic_polyfill::{AtomicU32, AtomicU8}; +use atomic_polyfill::{AtomicU64, AtomicU8}; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::extra::U8; @@ -1305,7 +1305,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { // other way. pub struct State { users: AtomicU8, - used_pins: AtomicU32, + used_pins: AtomicU64, } fn on_pio_drop() { @@ -1313,8 +1313,7 @@ fn on_pio_drop() { if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { let used_pins = state.used_pins.load(Ordering::Relaxed); let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; - // we only have 30 pins. don't test the other two since gpio() asserts. - for i in 0..30 { + for i in 0..crate::gpio::BANK0_PIN_COUNT { if used_pins & (1 << i) != 0 { pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); } @@ -1339,7 +1338,7 @@ trait SealedInstance { fn state() -> &'static State { static STATE: State = State { users: AtomicU8::new(0), - used_pins: AtomicU32::new(0), + used_pins: AtomicU64::new(0), }; &STATE From 8519e54461b8d3c3c2a97c88823498c878187ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20H=C3=A9riveaux?= Date: Mon, 16 Sep 2024 11:48:54 +0200 Subject: [PATCH 087/127] fix(boot): return signature error when no features Always return signature error in verify_and_mark_updated when no signature features are enabled. --- embassy-boot/src/firmware_updater/asynch.rs | 12 ++++++++---- embassy-boot/src/firmware_updater/blocking.rs | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs index 26f65f295..86b441592 100644 --- a/embassy-boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/src/firmware_updater/asynch.rs @@ -107,7 +107,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { let mut message = [0; 64]; self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - public_key.verify(&message, &signature).map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated().await; } #[cfg(feature = "ed25519-salty")] { @@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { message, r.is_ok() ); - r.map_err(into_signature_error)? + r.map_err(into_signature_error)?; + return self.state.mark_updated().await; + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) } - - self.state.mark_updated().await } /// Verify the update in DFU with any digest. diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index 35772a856..d3c723456 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> let mut chunk_buf = [0; 2]; self.hash::(_update_len, &mut chunk_buf, &mut message)?; - public_key.verify(&message, &signature).map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated(); } #[cfg(feature = "ed25519-salty")] { @@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> message, r.is_ok() ); - r.map_err(into_signature_error)? + r.map_err(into_signature_error)?; + return self.state.mark_updated(); + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) } - - self.state.mark_updated() } /// Verify the update in DFU with any digest. From 313e76af043c8b2bb2b31f8cc0eba7578ea41357 Mon Sep 17 00:00:00 2001 From: Sebastian Quilitz Date: Mon, 16 Sep 2024 12:26:00 +0200 Subject: [PATCH 088/127] rp: add constructor for tx-only blocking UART --- embassy-rp/src/uart/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index aba4b792a..c94e5e185 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -224,6 +224,17 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } impl<'d, T: Instance> UartTx<'d, T, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(None) + } + /// Convert this uart TX instance into a buffered uart using the provided /// irq and transmit buffer. pub fn into_buffered( From 48fd0550d1569a44a6b58a41423b0f86270fb373 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 16 Sep 2024 12:41:12 -0400 Subject: [PATCH 089/127] Review fixes Don't overflow on subtract Replace giant match with for loop dedupe register write --- embassy-rp/src/pio/mod.rs | 130 +++++++++++++------------------------- 1 file changed, 43 insertions(+), 87 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index fc940b045..ace597f00 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -767,95 +767,51 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { #[cfg(feature = "_rp235x")] { - let shift_gpio_base = { - match ( - if config.in_count > 0 { - Some(config.pins.in_base) - } else { - None - }, - if config.pins.sideset_count > 0 { - Some(config.pins.sideset_base) - } else { - None - }, - if config.pins.set_count > 0 { - Some(config.pins.set_base) - } else { - None - }, - if config.pins.out_count > 0 { - Some(config.pins.out_base) - } else { - None - }, - ) { - (None, None, None, None) => false, - - (Some(..32), None, None, None) => false, - (None, Some(..32), None, None) => false, - (None, None, Some(..32), None) => false, - (None, None, None, Some(..32)) => false, - - (Some(..32), Some(..32), None, None) => false, - (None, Some(..32), Some(..32), None) => false, - (None, None, Some(..32), Some(..32)) => false, - (Some(..32), None, None, Some(..32)) => false, - - (None, Some(..32), Some(..32), Some(..32)) => false, - (Some(..32), None, Some(..32), Some(..32)) => false, - (Some(..32), Some(..32), None, Some(..32)) => false, - (Some(..32), Some(..32), Some(..32), None) => false, - - (Some(..32), Some(..32), Some(..32), Some(..32)) => false, - - (Some(16..), None, None, None) => true, - (None, Some(16..), None, None) => true, - (None, None, Some(16..), None) => true, - (None, None, None, Some(16..)) => true, - - (Some(16..), Some(16..), None, None) => true, - (None, Some(16..), Some(16..), None) => true, - (None, None, Some(16..), Some(16..)) => true, - (Some(16..), None, None, Some(16..)) => true, - - (None, Some(16..), Some(16..), Some(16..)) => true, - (Some(16..), None, Some(16..), Some(16..)) => true, - (Some(16..), Some(16..), None, Some(16..)) => true, - (Some(16..), Some(16..), Some(16..), None) => true, - - (Some(16..), Some(16..), Some(16..), Some(16..)) => true, - - (i, side, set, out) => panic!( - "All pins must either be <=31 or >=16, in:{:?}, side:{:?}, set:{:?}, out:{:?}", - i, side, set, out - ), - } - }; - - if shift_gpio_base { - sm.pinctrl().write(|w| { - w.set_sideset_count(config.pins.sideset_count); - w.set_set_count(config.pins.set_count); - w.set_out_count(config.pins.out_count); - w.set_in_base(config.pins.in_base - 16); - w.set_sideset_base(config.pins.sideset_base - 16); - w.set_set_base(config.pins.set_base - 16); - w.set_out_base(config.pins.out_base - 16); - }); - } else { - sm.pinctrl().write(|w| { - w.set_sideset_count(config.pins.sideset_count); - w.set_set_count(config.pins.set_count); - w.set_out_count(config.pins.out_count); - w.set_in_base(config.pins.in_base); - w.set_sideset_base(config.pins.sideset_base); - w.set_set_base(config.pins.set_base); - w.set_out_base(config.pins.out_base); - }); + let mut low_ok = true; + let mut high_ok = true; + for pin in [ + config.pins.in_base, + config.pins.in_base + config.in_count, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count, + config.pins.set_base, + config.pins.set_base + config.pins.set_count, + config.pins.out_base, + config.pins.out_base + config.pins.out_count, + ] + .iter() + .flatten() + { + low_ok &= pin < 32; + high_ok &= pin >= 16; } - PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift_gpio_base)); + if !low_ok && !high_ok { + panic!( + "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", + config.pins.in_base, + config.pins.in_base + config.in_count, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count, + config.pins.set_base, + config.pins.set_base + config.pins.set_count, + config.pins.out_base, + config.pins.out_base + config.pins.out_count, + ) + } + let shift = if low_ok { 0 } else { 16 }; + + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default()); + w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default()); + w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default()); + w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default()); + }); + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16)); } if let Some(origin) = config.origin { From 9cfde66446ca5451a6cab80e0f2f783199fbeb62 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 16 Sep 2024 12:47:07 -0400 Subject: [PATCH 090/127] Move pin isolation config to make_pio_pin --- embassy-rp/src/pio/mod.rs | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index ace597f00..b08f2df6b 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -655,10 +655,6 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// Set pin used to signal jump. pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { - #[cfg(feature = "_rp235x")] - pin.pin.pad_ctrl().modify(|w| { - w.set_iso(false); - }); self.exec.jmp_pin = pin.pin(); } @@ -668,12 +664,6 @@ impl<'d, PIO: Instance> Config<'d, PIO> { pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { assert!(pins.len() <= 5); assert_consecutive(pins); - #[cfg(feature = "_rp235x")] - for pin in pins { - pin.pin.pad_ctrl().modify(|w| { - w.set_iso(false); - }) - } self.pins.set_base = pins.first().map_or(0, |p| p.pin()); self.pins.set_count = pins.len() as u8; } @@ -683,12 +673,6 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// effective. pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { assert_consecutive(pins); - #[cfg(feature = "_rp235x")] - for pin in pins { - pin.pin.pad_ctrl().modify(|w| { - w.set_iso(false); - }) - } self.pins.out_base = pins.first().map_or(0, |p| p.pin()); self.pins.out_count = pins.len() as u8; } @@ -698,12 +682,6 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// effective. pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { assert_consecutive(pins); - #[cfg(feature = "_rp235x")] - for pin in pins { - pin.pin.pad_ctrl().modify(|w| { - w.set_iso(false); - }) - } self.pins.in_base = pins.first().map_or(0, |p| p.pin()); self.in_count = pins.len() as u8; } @@ -778,10 +756,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { config.pins.set_base + config.pins.set_count, config.pins.out_base, config.pins.out_base + config.pins.out_count, - ] - .iter() - .flatten() - { + ] { low_ok &= pin < 32; high_ok &= pin >= 16; } @@ -1080,6 +1055,10 @@ impl<'d, PIO: Instance> Common<'d, PIO> { pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { into_ref!(pin); pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + #[cfg(feature = "_rp235x")] + pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); // we can be relaxed about this because we're &mut here and nothing is cached PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); Pin { From 6e0c3e25fd8e07ce3ee46e0ba74064a4c4574e16 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 16 Sep 2024 12:58:23 -0400 Subject: [PATCH 091/127] Only check ping groups that have count >0 --- embassy-rp/src/pio/mod.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index b08f2df6b..29a5bfab3 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -743,22 +743,21 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_out_base(config.pins.out_base); }); - #[cfg(feature = "_rp235x")] + //#[cfg(feature = "_rp235x")] { let mut low_ok = true; let mut high_ok = true; - for pin in [ - config.pins.in_base, - config.pins.in_base + config.in_count, - config.pins.sideset_base, - config.pins.sideset_base + config.pins.sideset_count, - config.pins.set_base, - config.pins.set_base + config.pins.set_count, - config.pins.out_base, - config.pins.out_base + config.pins.out_count, - ] { - low_ok &= pin < 32; - high_ok &= pin >= 16; + + let in_pins = config.pins.in_base..config.pins.in_base + config.in_count; + let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count; + let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count; + let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count; + + for pin_range in [in_pins, side_pins, set_pins, out_pins] { + for pin in pin_range { + low_ok &= pin < 32; + high_ok &= pin >= 16; + } } if !low_ok && !high_ok { From 8b34c94ef3640b2ec28b3de3c5d90db40b0b0ecd Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 16 Sep 2024 13:02:35 -0400 Subject: [PATCH 092/127] Improve error message when pin groups are not allowed --- embassy-rp/src/pio/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 29a5bfab3..b2f9abaa0 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -764,13 +764,13 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { panic!( "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", config.pins.in_base, - config.pins.in_base + config.in_count, + config.pins.in_base + config.in_count - 1, config.pins.sideset_base, - config.pins.sideset_base + config.pins.sideset_count, + config.pins.sideset_base + config.pins.sideset_count - 1, config.pins.set_base, - config.pins.set_base + config.pins.set_count, + config.pins.set_base + config.pins.set_count - 1, config.pins.out_base, - config.pins.out_base + config.pins.out_count, + config.pins.out_base + config.pins.out_count - 1, ) } let shift = if low_ok { 0 } else { 16 }; From d1508cc49c9ac029ececb1ae5e4c326ac678d1ec Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 16 Sep 2024 13:07:16 -0400 Subject: [PATCH 093/127] oops --- embassy-rp/src/pio/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index b2f9abaa0..72aa8f104 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -743,7 +743,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_out_base(config.pins.out_base); }); - //#[cfg(feature = "_rp235x")] + #[cfg(feature = "_rp235x")] { let mut low_ok = true; let mut high_ok = true; From 2855e65cc6ac5a3a102f3a309b4c450efffe1417 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 16 Sep 2024 13:24:47 -0400 Subject: [PATCH 094/127] Disable pad isolation on clock in/out pins --- embassy-rp/src/clocks.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index ed146844c..f229a5acd 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -847,6 +847,10 @@ impl<'d, T: GpinPin> Gpin<'d, T> { into_ref!(gpin); gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); Gpin { gpin: gpin.map_into(), @@ -861,6 +865,7 @@ impl<'d, T: GpinPin> Gpin<'d, T> { impl<'d, T: GpinPin> Drop for Gpin<'d, T> { fn drop(&mut self) { + self.gpin.pad_ctrl().write(|_| {}); self.gpin .gpio() .ctrl() @@ -921,11 +926,15 @@ pub struct Gpout<'d, T: GpoutPin> { } impl<'d, T: GpoutPin> Gpout<'d, T> { - /// Create new general purpose cloud output. + /// Create new general purpose clock output. pub fn new(gpout: impl Peripheral

+ 'd) -> Self { into_ref!(gpout); gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpout.pad_ctrl().write(|w| { + w.set_iso(false); + }); Self { gpout } } @@ -1005,6 +1014,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { fn drop(&mut self) { self.disable(); + self.gpout.pad_ctrl().write(|_| {}); self.gpout .gpio() .ctrl() From be0d9775e3bcc3c1bd1448e357d7c6cd67b68991 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 11 Sep 2024 22:06:26 +0200 Subject: [PATCH 095/127] net: refactor to simplify lifetimes/generics. --- ci.sh | 2 +- embassy-net/Cargo.toml | 10 +- embassy-net/README.md | 3 +- embassy-net/src/dns.rs | 25 +- embassy-net/src/{device.rs => driver_util.rs} | 2 +- embassy-net/src/lib.rs | 445 ++++++++---------- embassy-net/src/raw.rs | 53 ++- embassy-net/src/tcp.rs | 76 +-- embassy-net/src/udp.rs | 61 +-- .../nrf52840/src/bin/ethernet_enc28j60.rs | 14 +- examples/nrf52840/src/bin/usb_ethernet.rs | 11 +- examples/nrf52840/src/bin/wifi_esp_hosted.rs | 11 +- examples/nrf9160/src/bin/modem_tcp_client.rs | 16 +- .../rp/src/bin/ethernet_w5500_multisocket.rs | 19 +- .../rp/src/bin/ethernet_w5500_tcp_client.rs | 13 +- .../rp/src/bin/ethernet_w5500_tcp_server.rs | 13 +- examples/rp/src/bin/ethernet_w5500_udp.rs | 13 +- examples/rp/src/bin/usb_ethernet.rs | 11 +- examples/rp/src/bin/wifi_ap_tcp_server.rs | 16 +- examples/rp/src/bin/wifi_scan.rs | 5 +- examples/rp/src/bin/wifi_tcp_server.rs | 16 +- examples/rp/src/bin/wifi_webrequest.rs | 16 +- examples/std/src/bin/net.rs | 11 +- examples/std/src/bin/net_dns.rs | 11 +- examples/std/src/bin/net_ppp.rs | 17 +- examples/std/src/bin/net_udp.rs | 11 +- examples/std/src/bin/tcp_accept.rs | 11 +- examples/stm32f4/src/bin/eth.rs | 13 +- examples/stm32f4/src/bin/eth_w5500.rs | 11 +- examples/stm32f4/src/bin/usb_ethernet.rs | 11 +- examples/stm32f7/src/bin/eth.rs | 13 +- examples/stm32h5/src/bin/eth.rs | 13 +- examples/stm32h7/src/bin/eth.rs | 13 +- examples/stm32h7/src/bin/eth_client.rs | 13 +- examples/stm32h7/src/bin/eth_client_mii.rs | 13 +- .../src/bin/spe_adin1110_http_server.rs | 11 +- examples/stm32l5/src/bin/usb_ethernet.rs | 11 +- tests/nrf/src/bin/ethernet_enc28j60_perf.rs | 11 +- tests/nrf/src/bin/wifi_esp_hosted_perf.rs | 13 +- tests/perf-client/src/lib.rs | 9 +- tests/rp/src/bin/cyw43-perf.rs | 13 +- tests/rp/src/bin/ethernet_w5100s_perf.rs | 13 +- tests/stm32/src/bin/eth.rs | 11 +- 43 files changed, 500 insertions(+), 604 deletions(-) rename embassy-net/src/{device.rs => driver_util.rs} (98%) diff --git a/ci.sh b/ci.sh index 8fef731a4..5e8bddb8c 100755 --- a/ci.sh +++ b/ci.sh @@ -47,7 +47,7 @@ cargo batch \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 28bac485b..2e21b4231 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -16,11 +16,11 @@ categories = [ [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/" -features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] +features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] target = "thumbv7em-none-eabi" [package.metadata.docs.rs] -features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] +features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] [features] default = [] @@ -60,15 +60,15 @@ medium-ethernet = ["smoltcp/medium-ethernet"] medium-ip = ["smoltcp/medium-ip"] ## Enable the IEEE 802.15.4 medium medium-ieee802154 = ["smoltcp/medium-ieee802154"] -## Enable IGMP support -igmp = ["smoltcp/proto-igmp"] +## Enable multicast support (for both ipv4 and/or ipv6 if enabled) +multicast = ["smoltcp/multicast"] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -smoltcp = { version = "0.11.0", default-features = false, features = [ +smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [ "socket", "async", ] } diff --git a/embassy-net/README.md b/embassy-net/README.md index 6b7d46934..1722ffc7b 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -10,8 +10,9 @@ memory management designed to work well for embedded systems, aiming for a more - IPv4, IPv6 - Ethernet and bare-IP mediums. -- TCP, UDP, DNS, DHCPv4, IGMPv4 +- TCP, UDP, DNS, DHCPv4 - TCP sockets implement the `embedded-io` async traits. +- Multicast See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and unimplemented features of the network protocols. diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 8ccfa4e4f..1fbaea4f0 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -9,7 +9,7 @@ pub use smoltcp::socket::dns::{DnsQuery, Socket}; pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; -use crate::{Driver, Stack}; +use crate::Stack; /// Errors returned by DnsSocket. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -44,21 +44,15 @@ impl From for Error { /// This exists only for compatibility with crates that use `embedded-nal-async`. /// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're /// not using `embedded-nal-async`. -pub struct DnsSocket<'a, D> -where - D: Driver + 'static, -{ - stack: &'a Stack, +pub struct DnsSocket<'a> { + stack: Stack<'a>, } -impl<'a, D> DnsSocket<'a, D> -where - D: Driver + 'static, -{ +impl<'a> DnsSocket<'a> { /// Create a new DNS socket using the provided stack. /// /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. - pub fn new(stack: &'a Stack) -> Self { + pub fn new(stack: Stack<'a>) -> Self { Self { stack } } @@ -72,10 +66,7 @@ where } } -impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D> -where - D: Driver + 'static, -{ +impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { type Error = Error; async fn get_host_by_name( @@ -124,3 +115,7 @@ where todo!() } } + +fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> { + x +} diff --git a/embassy-net/src/device.rs b/embassy-net/src/driver_util.rs similarity index 98% rename from embassy-net/src/device.rs rename to embassy-net/src/driver_util.rs index 3b1d3c47c..b2af1d499 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/driver_util.rs @@ -74,7 +74,7 @@ where { fn consume(self, f: F) -> R where - F: FnOnce(&mut [u8]) -> R, + F: FnOnce(&[u8]) -> R, { self.0.consume(|buf| { #[cfg(feature = "packet-trace")] diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 56321cec9..ef53fb905 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -12,9 +12,9 @@ compile_error!("You must enable at least one of the following features: proto-ip // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -mod device; #[cfg(feature = "dns")] pub mod dns; +mod driver_util; #[cfg(feature = "raw")] pub mod raw; #[cfg(feature = "tcp")] @@ -25,6 +25,7 @@ pub mod udp; use core::cell::RefCell; use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; use core::pin::pin; use core::task::{Context, Poll}; @@ -36,7 +37,7 @@ use embassy_time::{Instant, Timer}; use heapless::Vec; #[cfg(feature = "dns")] pub use smoltcp::config::DNS_MAX_SERVER_COUNT; -#[cfg(feature = "igmp")] +#[cfg(feature = "multicast")] pub use smoltcp::iface::MulticastError; #[allow(unused_imports)] use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; @@ -57,7 +58,7 @@ pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; #[cfg(feature = "proto-ipv6")] pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; -use crate::device::DriverAdapter; +use crate::driver_util::DriverAdapter; use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; const LOCAL_PORT_MIN: u16 = 1025; @@ -69,33 +70,33 @@ const MAX_HOSTNAME_LEN: usize = 32; /// Memory resources needed for a network stack. pub struct StackResources { - sockets: [SocketStorage<'static>; SOCK], + sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>, + inner: MaybeUninit>, #[cfg(feature = "dns")] - queries: [Option; MAX_QUERIES], + queries: MaybeUninit<[Option; MAX_QUERIES]>, #[cfg(feature = "dhcpv4-hostname")] - hostname: core::cell::UnsafeCell, + hostname: HostnameResources, } #[cfg(feature = "dhcpv4-hostname")] struct HostnameResources { - option: smoltcp::wire::DhcpOption<'static>, - data: [u8; MAX_HOSTNAME_LEN], + option: MaybeUninit>, + data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>, } impl StackResources { /// Create a new set of stack resources. pub const fn new() -> Self { - #[cfg(feature = "dns")] - const INIT: Option = None; Self { - sockets: [SocketStorage::EMPTY; SOCK], + sockets: MaybeUninit::uninit(), + inner: MaybeUninit::uninit(), #[cfg(feature = "dns")] - queries: [INIT; MAX_QUERIES], + queries: MaybeUninit::uninit(), #[cfg(feature = "dhcpv4-hostname")] - hostname: core::cell::UnsafeCell::new(HostnameResources { - option: smoltcp::wire::DhcpOption { kind: 0, data: &[] }, - data: [0; MAX_HOSTNAME_LEN], - }), + hostname: HostnameResources { + option: MaybeUninit::uninit(), + data: MaybeUninit::uninit(), + }, } } } @@ -239,16 +240,29 @@ pub enum ConfigV6 { Static(StaticConfigV6), } -/// A network stack. +/// Network stack runner. /// -/// This is the main entry point for the network stack. -pub struct Stack { - pub(crate) socket: RefCell, - inner: RefCell>, +/// You must call [`Runner::run()`] in a background task for the network stack to work. +pub struct Runner<'d, D: Driver> { + driver: D, + stack: Stack<'d>, } -struct Inner { - device: D, +/// Network stack handle +/// +/// Use this to create sockets. It's `Copy`, so you can pass +/// it by value instead of by reference. +#[derive(Copy, Clone)] +pub struct Stack<'d> { + inner: &'d RefCell, +} + +pub(crate) struct Inner { + pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. + pub(crate) iface: Interface, + pub(crate) waker: WakerRegistration, + hardware_address: HardwareAddress, + next_local_port: u16, link_up: bool, #[cfg(feature = "proto-ipv4")] static_v4: Option, @@ -262,14 +276,83 @@ struct Inner { #[cfg(feature = "dns")] dns_waker: WakerRegistration, #[cfg(feature = "dhcpv4-hostname")] - hostname: &'static mut core::cell::UnsafeCell, + hostname: *mut HostnameResources, } -pub(crate) struct SocketStack { - pub(crate) sockets: SocketSet<'static>, - pub(crate) iface: Interface, - pub(crate) waker: WakerRegistration, - next_local_port: u16, +fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> { + x +} + +/// Create a new network stack. +pub fn new<'d, D: Driver, const SOCK: usize>( + mut driver: D, + config: Config, + resources: &'d mut StackResources, + random_seed: u64, +) -> (Stack<'d>, Runner<'d, D>) { + let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + let mut iface_cfg = smoltcp::iface::Config::new(hardware_address); + iface_cfg.random_seed = random_seed; + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut driver, + cx: None, + medium, + }, + instant_to_smoltcp(Instant::now()), + ); + + unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] { + core::mem::transmute(x) + } + + let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]); + #[allow(unused_mut)] + let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) }); + + let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + #[cfg(feature = "dns")] + let dns_socket = sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(unsafe { + transmute_slice(resources.queries.write([const { None }; MAX_QUERIES])) + }), + )); + + let mut inner = Inner { + sockets, + iface, + waker: WakerRegistration::new(), + next_local_port, + hardware_address, + link_up: false, + #[cfg(feature = "proto-ipv4")] + static_v4: None, + #[cfg(feature = "proto-ipv6")] + static_v6: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + config_waker: WakerRegistration::new(), + #[cfg(feature = "dns")] + dns_socket, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: &mut resources.hostname, + }; + + #[cfg(feature = "proto-ipv4")] + inner.set_config_v4(config.ipv4); + #[cfg(feature = "proto-ipv6")] + inner.set_config_v6(config.ipv6); + inner.apply_static_config(); + + let inner = &*resources.inner.write(RefCell::new(inner)); + let stack = Stack { inner }; + (stack, Runner { driver, stack }) } fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) { @@ -292,89 +375,23 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddres } } -impl Stack { - /// Create a new network stack. - pub fn new( - mut device: D, - config: Config, - resources: &'static mut StackResources, - random_seed: u64, - ) -> Self { - let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address()); - let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr); - iface_cfg.random_seed = random_seed; - - let iface = Interface::new( - iface_cfg, - &mut DriverAdapter { - inner: &mut device, - cx: None, - medium, - }, - instant_to_smoltcp(Instant::now()), - ); - - let sockets = SocketSet::new(&mut resources.sockets[..]); - - let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; - - #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))] - let mut socket = SocketStack { - sockets, - iface, - waker: WakerRegistration::new(), - next_local_port, - }; - - let mut inner = Inner { - device, - link_up: false, - #[cfg(feature = "proto-ipv4")] - static_v4: None, - #[cfg(feature = "proto-ipv6")] - static_v6: None, - #[cfg(feature = "dhcpv4")] - dhcp_socket: None, - config_waker: WakerRegistration::new(), - #[cfg(feature = "dns")] - dns_socket: socket.sockets.add(dns::Socket::new( - &[], - managed::ManagedSlice::Borrowed(&mut resources.queries), - )), - #[cfg(feature = "dns")] - dns_waker: WakerRegistration::new(), - #[cfg(feature = "dhcpv4-hostname")] - hostname: &mut resources.hostname, - }; - - #[cfg(feature = "proto-ipv4")] - inner.set_config_v4(&mut socket, config.ipv4); - #[cfg(feature = "proto-ipv6")] - inner.set_config_v6(&mut socket, config.ipv6); - inner.apply_static_config(&mut socket); - - Self { - socket: RefCell::new(socket), - inner: RefCell::new(inner), - } +impl<'d> Stack<'d> { + fn with(&self, f: impl FnOnce(&Inner) -> R) -> R { + f(&*self.inner.borrow()) } - fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { - f(&*self.socket.borrow(), &*self.inner.borrow()) - } - - fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { - f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) + fn with_mut(&self, f: impl FnOnce(&mut Inner) -> R) -> R { + f(&mut *self.inner.borrow_mut()) } /// Get the hardware address of the network interface. pub fn hardware_address(&self) -> HardwareAddress { - self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0) + self.with(|i| i.hardware_address) } /// Get whether the link is up. pub fn is_link_up(&self) -> bool { - self.with(|_s, i| i.link_up) + self.with(|i| i.link_up) } /// Get whether the network stack has a valid IP configuration. @@ -420,15 +437,14 @@ impl Stack { /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP). /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it. /// static RESOURCES: StaticCell> = StaticCell::new(); - /// static STACK: StaticCell = StaticCell::new(); - /// let stack = &*STACK.init(embassy_net::Stack::new( - /// device, + /// let (stack, runner) = embassy_net::new( + /// driver, /// config, /// RESOURCES.init(embassy_net::StackResources::new()), /// seed - /// )); - /// // Launch network task that runs `stack.run().await` - /// spawner.spawn(net_task(stack)).unwrap(); + /// ); + /// // Launch network task that runs `runner.run().await` + /// spawner.spawn(net_task(runner)).unwrap(); /// // Wait for DHCP config /// stack.wait_config_up().await; /// // use the network stack @@ -448,7 +464,7 @@ impl Stack { // when a config is applied (static or DHCP). trace!("Waiting for config up"); - self.with_mut(|_, i| { + self.with_mut(|i| { i.config_waker.register(cx.waker()); }); @@ -464,45 +480,33 @@ impl Stack { /// acquire an IP address, or Some if it has. #[cfg(feature = "proto-ipv4")] pub fn config_v4(&self) -> Option { - self.with(|_, i| i.static_v4.clone()) + self.with(|i| i.static_v4.clone()) } /// Get the current IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn config_v6(&self) -> Option { - self.with(|_, i| i.static_v6.clone()) + self.with(|i| i.static_v6.clone()) } /// Set the IPv4 configuration. #[cfg(feature = "proto-ipv4")] pub fn set_config_v4(&self, config: ConfigV4) { - self.with_mut(|s, i| { - i.set_config_v4(s, config); - i.apply_static_config(s); + self.with_mut(|i| { + i.set_config_v4(config); + i.apply_static_config(); }) } /// Set the IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn set_config_v6(&self, config: ConfigV6) { - self.with_mut(|s, i| { - i.set_config_v6(s, config); - i.apply_static_config(s); + self.with_mut(|i| { + i.set_config_v6(config); + i.apply_static_config(); }) } - /// Run the network stack. - /// - /// You must call this in a background task, to process network events. - pub async fn run(&self) -> ! { - poll_fn(|cx| { - self.with_mut(|s, i| i.poll(cx, s)); - Poll::<()>::Pending - }) - .await; - unreachable!() - } - /// Make a query for a given name and return the corresponding IP addresses. #[cfg(feature = "dns")] pub async fn dns_query( @@ -528,11 +532,11 @@ impl Stack { } let query = poll_fn(|cx| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); - match socket.start_query(s.iface.context(), name, qtype) { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.start_query(i.iface.context(), name, qtype) { Ok(handle) => { - s.waker.wake(); + i.waker.wake(); Poll::Ready(Ok(handle)) } Err(dns::StartQueryError::NoFreeSlot) => { @@ -569,17 +573,17 @@ impl Stack { } let drop = OnDrop::new(|| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); socket.cancel_query(query); - s.waker.wake(); + i.waker.wake(); i.dns_waker.wake(); }) }); let res = poll_fn(|cx| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); match socket.get_query_result(query) { Ok(addrs) => { i.dns_waker.wake(); @@ -604,104 +608,34 @@ impl Stack { } } -#[cfg(feature = "igmp")] -impl Stack { +#[cfg(feature = "multicast")] +impl<'d> Stack<'d> { /// Join a multicast group. - pub async fn join_multicast_group(&self, addr: T) -> Result - where - T: Into, - { - let addr = addr.into(); - - poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await - } - - /// Join a multicast group. - /// - /// When the send queue is full, this method will return `Poll::Pending` - /// and register the current task to be notified when the queue has space available. - pub fn poll_join_multicast_group(&self, addr: T, cx: &mut Context<'_>) -> Poll> - where - T: Into, - { - let addr = addr.into(); - - self.with_mut(|s, i| { - let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address()); - let mut smoldev = DriverAdapter { - cx: Some(cx), - inner: &mut i.device, - medium, - }; - - match s - .iface - .join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now())) - { - Ok(announce_sent) => Poll::Ready(Ok(announce_sent)), - Err(MulticastError::Exhausted) => Poll::Pending, - Err(other) => Poll::Ready(Err(other)), - } - }) + pub fn join_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.join_multicast_group(addr)) } /// Leave a multicast group. - pub async fn leave_multicast_group(&self, addr: T) -> Result - where - T: Into, - { - let addr = addr.into(); - - poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await - } - - /// Leave a multicast group. - /// - /// When the send queue is full, this method will return `Poll::Pending` - /// and register the current task to be notified when the queue has space available. - pub fn poll_leave_multicast_group(&self, addr: T, cx: &mut Context<'_>) -> Poll> - where - T: Into, - { - let addr = addr.into(); - - self.with_mut(|s, i| { - let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address()); - let mut smoldev = DriverAdapter { - cx: Some(cx), - inner: &mut i.device, - medium, - }; - - match s - .iface - .leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now())) - { - Ok(leave_sent) => Poll::Ready(Ok(leave_sent)), - Err(MulticastError::Exhausted) => Poll::Pending, - Err(other) => Poll::Ready(Err(other)), - } - }) + pub fn leave_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.leave_multicast_group(addr)) } /// Get whether the network stack has joined the given multicast group. - pub fn has_multicast_group>(&self, addr: T) -> bool { - self.socket.borrow().iface.has_multicast_group(addr) + pub fn has_multicast_group(&self, addr: impl Into) -> bool { + self.with(|i| i.iface.has_multicast_group(addr)) } } -impl SocketStack { +impl Inner { #[allow(clippy::absurd_extreme_comparisons, dead_code)] pub fn get_local_port(&mut self) -> u16 { let res = self.next_local_port; self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; res } -} -impl Inner { #[cfg(feature = "proto-ipv4")] - pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) { + pub fn set_config_v4(&mut self, config: ConfigV4) { // Handle static config. self.static_v4 = match config.clone() { ConfigV4::None => None, @@ -717,12 +651,12 @@ impl Inner { // Create the socket if it doesn't exist. if self.dhcp_socket.is_none() { let socket = smoltcp::socket::dhcpv4::Socket::new(); - let handle = _s.sockets.add(socket); + let handle = self.sockets.add(socket); self.dhcp_socket = Some(handle); } // Configure it - let socket = _s.sockets.get_mut::(unwrap!(self.dhcp_socket)); + let socket = self.sockets.get_mut::(unwrap!(self.dhcp_socket)); socket.set_ignore_naks(c.ignore_naks); socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp)); socket.set_ports(c.server_port, c.client_port); @@ -731,19 +665,20 @@ impl Inner { socket.set_outgoing_options(&[]); #[cfg(feature = "dhcpv4-hostname")] if let Some(h) = c.hostname { - // safety: we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. - let hostname = unsafe { &mut *self.hostname.get() }; + // safety: + // - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. + // - we know this pointer lives for as long as the stack exists, because `new()` borrows + // the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp. + let hostname = unsafe { &mut *self.hostname }; // create data - // safety: we know the buffer lives forever, new borrows the StackResources for 'static. - // also we won't modify it until next call to this function. - hostname.data[..h.len()].copy_from_slice(h.as_bytes()); - let data: &[u8] = &hostname.data[..h.len()]; - let data: &'static [u8] = unsafe { core::mem::transmute(data) }; + let data = hostname.data.write([0; MAX_HOSTNAME_LEN]); + data[..h.len()].copy_from_slice(h.as_bytes()); + let data: &[u8] = &data[..h.len()]; // set the option. - hostname.option = smoltcp::wire::DhcpOption { data, kind: 12 }; - socket.set_outgoing_options(core::slice::from_ref(&hostname.option)); + let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 }); + socket.set_outgoing_options(core::slice::from_ref(option)); } socket.reset(); @@ -751,7 +686,7 @@ impl Inner { _ => { // Remove DHCP socket if any. if let Some(socket) = self.dhcp_socket { - _s.sockets.remove(socket); + self.sockets.remove(socket); self.dhcp_socket = None; } } @@ -759,14 +694,14 @@ impl Inner { } #[cfg(feature = "proto-ipv6")] - pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) { + pub fn set_config_v6(&mut self, config: ConfigV6) { self.static_v6 = match config { ConfigV6::None => None, ConfigV6::Static(c) => Some(c), }; } - fn apply_static_config(&mut self, s: &mut SocketStack) { + fn apply_static_config(&mut self) { let mut addrs = Vec::new(); #[cfg(feature = "dns")] let mut dns_servers: Vec<_, 6> = Vec::new(); @@ -810,20 +745,20 @@ impl Inner { } // Apply addresses - s.iface.update_ip_addrs(|a| *a = addrs); + self.iface.update_ip_addrs(|a| *a = addrs); // Apply gateways #[cfg(feature = "proto-ipv4")] if let Some(gateway) = gateway_v4 { - unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway)); + unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway)); } else { - s.iface.routes_mut().remove_default_ipv4_route(); + self.iface.routes_mut().remove_default_ipv4_route(); } #[cfg(feature = "proto-ipv6")] if let Some(gateway) = gateway_v6 { - unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway)); + unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway)); } else { - s.iface.routes_mut().remove_default_ipv6_route(); + self.iface.routes_mut().remove_default_ipv6_route(); } // Apply DNS servers @@ -835,7 +770,7 @@ impl Inner { } else { dns_servers.len() }; - s.sockets + self.sockets .get_mut::(self.dns_socket) .update_servers(&dns_servers[..count]); } @@ -843,10 +778,10 @@ impl Inner { self.config_waker.wake(); } - fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { - s.waker.register(cx.waker()); + fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { + self.waker.register(cx.waker()); - let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address()); + let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address()); #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] { @@ -859,21 +794,21 @@ impl Inner { _ => false, }; if do_set { - s.iface.set_hardware_addr(_hardware_addr); + self.iface.set_hardware_addr(_hardware_addr); } } let timestamp = instant_to_smoltcp(Instant::now()); let mut smoldev = DriverAdapter { cx: Some(cx), - inner: &mut self.device, + inner: driver, medium, }; - s.iface.poll(timestamp, &mut smoldev, &mut s.sockets); + self.iface.poll(timestamp, &mut smoldev, &mut self.sockets); // Update link up let old_link_up = self.link_up; - self.link_up = self.device.link_state(cx) == LinkState::Up; + self.link_up = driver.link_state(cx) == LinkState::Up; // Print when changed if old_link_up != self.link_up { @@ -885,7 +820,7 @@ impl Inner { #[cfg(feature = "dhcpv4")] if let Some(dhcp_handle) = self.dhcp_socket { - let socket = s.sockets.get_mut::(dhcp_handle); + let socket = self.sockets.get_mut::(dhcp_handle); if self.link_up { if old_link_up != self.link_up { @@ -914,10 +849,10 @@ impl Inner { } if apply_config { - self.apply_static_config(s); + self.apply_static_config(); } - if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { + if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) { let t = pin!(Timer::at(instant_from_smoltcp(poll_at))); if t.poll(cx).is_ready() { cx.waker().wake_by_ref(); @@ -925,3 +860,17 @@ impl Inner { } } } + +impl<'d, D: Driver> Runner<'d, D> { + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. + pub async fn run(&mut self) -> ! { + poll_fn(|cx| { + self.stack.with_mut(|i| i.poll(cx, &mut self.driver)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } +} diff --git a/embassy-net/src/raw.rs b/embassy-net/src/raw.rs index 7ecd913e7..1098dc208 100644 --- a/embassy-net/src/raw.rs +++ b/embassy-net/src/raw.rs @@ -1,6 +1,5 @@ //! Raw sockets. -use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::{Context, Poll}; @@ -11,7 +10,7 @@ use smoltcp::socket::raw; pub use smoltcp::socket::raw::PacketMetadata; use smoltcp::wire::{IpProtocol, IpVersion}; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by [`RawSocket::recv`] and [`RawSocket::send`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -23,14 +22,14 @@ pub enum RecvError { /// An Raw socket. pub struct RawSocket<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'a> RawSocket<'a> { /// Create a new Raw socket using the provided stack and buffers. pub fn new( - stack: &'a Stack, + stack: Stack<'a>, ip_version: IpVersion, ip_protocol: IpProtocol, rx_meta: &'a mut [PacketMetadata], @@ -38,31 +37,29 @@ impl<'a> RawSocket<'a> { tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - let s = &mut *stack.socket.borrow_mut(); + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(raw::Socket::new( + ip_version, + ip_protocol, + raw::PacketBuffer::new(rx_meta, rx_buffer), + raw::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); - let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(raw::Socket::new( - ip_version, - ip_protocol, - raw::PacketBuffer::new(rx_meta, rx_buffer), - raw::PacketBuffer::new(tx_meta, tx_buffer), - )); - - Self { - stack: &stack.socket, - handle, - } + Self { stack, handle } } fn with_mut(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } /// Receive a datagram. @@ -115,6 +112,10 @@ impl<'a> RawSocket<'a> { impl Drop for RawSocket<'_> { fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } + +fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> { + x +} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index b2e3279cc..bcddbc95b 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -8,12 +8,10 @@ //! Incoming connections when no socket is listening are rejected. To accept many incoming //! connections, create many sockets and put them all into listening mode. -use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::Poll; -use embassy_net_driver::Driver; use embassy_time::Duration; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::tcp; @@ -21,7 +19,7 @@ pub use smoltcp::socket::tcp::State; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; use crate::time::duration_to_smoltcp; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by TcpSocket read/write functions. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -157,20 +155,18 @@ impl<'a> TcpWriter<'a> { impl<'a> TcpSocket<'a> { /// Create a new TCP socket on the given stack, with the given buffers. - pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { - let s = &mut *stack.socket.borrow_mut(); - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(tcp::Socket::new( - tcp::SocketBuffer::new(rx_buffer), - tcp::SocketBuffer::new(tx_buffer), - )); + pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let handle = stack.with_mut(|i| { + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(tcp::Socket::new( + tcp::SocketBuffer::new(rx_buffer), + tcp::SocketBuffer::new(tx_buffer), + )) + }); Self { - io: TcpIo { - stack: &stack.socket, - handle, - }, + io: TcpIo { stack: stack, handle }, } } @@ -228,7 +224,7 @@ impl<'a> TcpSocket<'a> { where T: Into, { - let local_port = self.io.stack.borrow_mut().get_local_port(); + let local_port = self.io.stack.with_mut(|i| i.get_local_port()); match { self.io @@ -401,31 +397,43 @@ impl<'a> TcpSocket<'a> { impl<'a> Drop for TcpSocket<'a> { fn drop(&mut self) { - self.io.stack.borrow_mut().sockets.remove(self.io.handle); + self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle)); } } +fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> { + x +} +fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> { + x +} +fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> { + x +} + // ======================= #[derive(Copy, Clone)] struct TcpIo<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'d> TcpIo<'d> { fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.borrow(); - let socket = s.sockets.get::(self.handle); - f(socket, &s.iface) + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) } fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } async fn read(&mut self, buf: &mut [u8]) -> Result { @@ -676,15 +684,15 @@ pub mod client { /// TCP client connection pool compatible with `embedded-nal-async` traits. /// /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. - pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { - stack: &'d Stack, + pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + stack: Stack<'d>, state: &'d TcpClientState, socket_timeout: Option, } - impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> { /// Create a new `TcpClient`. - pub fn new(stack: &'d Stack, state: &'d TcpClientState) -> Self { + pub fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Self { Self { stack, state, @@ -701,8 +709,8 @@ pub mod client { } } - impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect - for TcpClient<'d, D, N, TX_SZ, RX_SZ> + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + for TcpClient<'d, N, TX_SZ, RX_SZ> { type Error = Error; type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; @@ -722,7 +730,7 @@ pub mod client { IpAddr::V6(_) => panic!("ipv6 support not enabled"), }; let remote_endpoint = (addr, remote.port()); - let mut socket = TcpConnection::new(&self.stack, self.state)?; + let mut socket = TcpConnection::new(self.stack, self.state)?; socket.socket.set_timeout(self.socket_timeout.clone()); socket .socket @@ -741,7 +749,7 @@ pub mod client { } impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> { - fn new(stack: &'d Stack, state: &'d TcpClientState) -> Result { + fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Result { let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; Ok(Self { socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 1d5360187..3eb6e2f83 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -1,17 +1,15 @@ //! UDP sockets. -use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::{Context, Poll}; -use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::udp; pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata}; use smoltcp::wire::IpListenEndpoint; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by [`UdpSocket::bind`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -43,34 +41,31 @@ pub enum RecvError { /// An UDP socket. pub struct UdpSocket<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'a> UdpSocket<'a> { /// Create a new UDP socket using the provided stack and buffers. - pub fn new( - stack: &'a Stack, + pub fn new( + stack: Stack<'a>, rx_meta: &'a mut [PacketMetadata], rx_buffer: &'a mut [u8], tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - let s = &mut *stack.socket.borrow_mut(); + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(udp::Socket::new( + udp::PacketBuffer::new(rx_meta, rx_buffer), + udp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); - let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(udp::Socket::new( - udp::PacketBuffer::new(rx_meta, rx_buffer), - udp::PacketBuffer::new(tx_meta, tx_buffer), - )); - - Self { - stack: &stack.socket, - handle, - } + Self { stack, handle } } /// Bind the socket to a local endpoint. @@ -82,7 +77,7 @@ impl<'a> UdpSocket<'a> { if endpoint.port == 0 { // If user didn't specify port allocate a dynamic port. - endpoint.port = self.stack.borrow_mut().get_local_port(); + endpoint.port = self.stack.with_mut(|i| i.get_local_port()); } match self.with_mut(|s, _| s.bind(endpoint)) { @@ -93,17 +88,19 @@ impl<'a> UdpSocket<'a> { } fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.borrow(); - let socket = s.sockets.get::(self.handle); - f(socket, &s.iface) + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) } fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } /// Receive a datagram. @@ -298,6 +295,10 @@ impl<'a> UdpSocket<'a> { impl Drop for UdpSocket<'_> { fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } + +fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> { + x +} diff --git a/examples/nrf52840/src/bin/ethernet_enc28j60.rs b/examples/nrf52840/src/bin/ethernet_enc28j60.rs index 94cf09c88..0946492fe 100644 --- a/examples/nrf52840/src/bin/ethernet_enc28j60.rs +++ b/examples/nrf52840/src/bin/ethernet_enc28j60.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_enc28j60::Enc28j60; use embassy_nrf::gpio::{Level, Output, OutputDrive}; use embassy_nrf::rng::Rng; @@ -23,11 +23,12 @@ bind_interrupts!(struct Irqs { #[embassy_executor::task] async fn net_task( - stack: &'static Stack< + mut runner: embassy_net::Runner< + 'static, Enc28j60, Output<'static>, Delay>, Output<'static>>, >, ) -> ! { - stack.run().await + runner.run().await } #[embassy_executor::main] @@ -67,12 +68,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell< - Stack, Output<'static>, Delay>, Output<'static>>>, - > = StaticCell::new(); - let stack = STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index e56b215e3..b07adac1f 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -6,7 +6,7 @@ use core::mem; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_nrf::rng::Rng; use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; use embassy_nrf::usb::Driver; @@ -39,8 +39,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -116,10 +116,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs index a3b69a99b..26eaf485e 100644 --- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -4,7 +4,7 @@ use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; @@ -36,8 +36,8 @@ async fn wifi_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -90,10 +90,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs index 5335b6b51..929883884 100644 --- a/examples/nrf9160/src/bin/modem_tcp_client.rs +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -46,15 +46,15 @@ async fn modem_task(runner: Runner<'static>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_nrf91::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::task] async fn control_task( control: &'static context::Control<'static>, config: context::Config<'static>, - stack: &'static Stack>, + stack: Stack<'static>, ) { unwrap!(control.configure(&config).await); unwrap!( @@ -150,15 +150,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::<2>::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); static CONTROL: StaticCell> = StaticCell::new(); let control = CONTROL.init(context::Control::new(control, 0).await); diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index aaa035a72..12003adbe 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -36,8 +36,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -71,17 +71,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -89,12 +88,12 @@ async fn main(spawner: Spawner) { info!("IP address: {:?}", local_addr); // Create two sockets listening to the same port, to handle simultaneous connections - unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); - unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 1, 1234))); } #[embassy_executor::task(pool_size = 2)] -async fn listen_task(stack: &'static Stack>, id: u8, port: u16) { +async fn listen_task(stack: Stack<'static>, id: u8, port: u16) { let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; let mut buf = [0; 4096]; @@ -131,7 +130,7 @@ async fn listen_task(stack: &'static Stack>, id: u8, port: u16) } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 8e96a114c..d66a43a88 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -38,8 +38,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -74,17 +74,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -119,7 +118,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 40736bf3c..97d9bd4c9 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -37,8 +37,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -73,17 +73,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -128,7 +127,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index c79f01538..b1b5f9758 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -36,8 +36,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -71,17 +71,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -108,7 +107,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 03c510f37..9a15125d4 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -8,7 +8,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_rp::clocks::RoscRng; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; @@ -40,8 +40,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -108,11 +108,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/rp/src/bin/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs index 00f404a9b..4c9651433 100644 --- a/examples/rp/src/bin/wifi_ap_tcp_server.rs +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -11,7 +11,7 @@ use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -33,8 +33,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -80,16 +80,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); //control.start_ap_open("cyw43", 5).await; control.start_ap_wpa2("cyw43", "password", 5).await; diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs index ab3529112..434f0074c 100644 --- a/examples/rp/src/bin/wifi_scan.rs +++ b/examples/rp/src/bin/wifi_scan.rs @@ -10,7 +10,6 @@ use core::str; use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; -use embassy_net::Stack; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; @@ -28,8 +27,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs index b2950d98a..7bf546e01 100644 --- a/examples/rp/src/bin/wifi_tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -12,7 +12,7 @@ use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -37,8 +37,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -84,16 +84,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { match control diff --git a/examples/rp/src/bin/wifi_webrequest.rs b/examples/rp/src/bin/wifi_webrequest.rs index b43be8905..1ae909917 100644 --- a/examples/rp/src/bin/wifi_webrequest.rs +++ b/examples/rp/src/bin/wifi_webrequest.rs @@ -13,7 +13,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::dns::DnsSocket; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -40,8 +40,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -87,16 +87,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { match control diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 310e7264d..cefa5448c 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -1,7 +1,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use embassy_time::Duration; use embedded_io_async::Write; @@ -22,8 +22,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -50,12 +50,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_buffer = [0; 4096]; diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs index c9615ef35..a42c5dbb7 100644 --- a/examples/std/src/bin/net_dns.rs +++ b/examples/std/src/bin/net_dns.rs @@ -1,7 +1,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::dns::DnsQueryType; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use heapless::Vec; use log::*; @@ -20,8 +20,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -49,12 +49,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack: &Stack<_> = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); let host = "example.com"; info!("querying host {:?}...", host); diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs index c5c27c4a3..7d0f1327f 100644 --- a/examples/std/src/bin/net_ppp.rs +++ b/examples/std/src/bin/net_ppp.rs @@ -37,16 +37,12 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_ppp::Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::task] -async fn ppp_task( - stack: &'static Stack>, - mut runner: Runner<'static>, - port: SerialPort, -) -> ! { +async fn ppp_task(stack: Stack<'static>, mut runner: Runner<'static>, port: SerialPort) -> ! { let port = Async::new(port).unwrap(); let port = BufReader::new(port); let port = adapter::FromFutures::new(port); @@ -97,17 +93,16 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, net_runner) = embassy_net::new( device, Config::default(), // don't configure IP yet RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(net_runner)).unwrap(); spawner.spawn(ppp_task(stack, runner, port)).unwrap(); // Then we can use it! diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index b2ba4915a..02d4d3efb 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs @@ -1,7 +1,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use heapless::Vec; use log::*; @@ -20,8 +20,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -48,12 +48,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_meta = [PacketMetadata::EMPTY; 16]; diff --git a/examples/std/src/bin/tcp_accept.rs b/examples/std/src/bin/tcp_accept.rs index 39b29a449..5d36b739d 100644 --- a/examples/std/src/bin/tcp_accept.rs +++ b/examples/std/src/bin/tcp_accept.rs @@ -3,7 +3,7 @@ use core::fmt::Write as _; use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use embassy_time::{Duration, Timer}; use embedded_io_async::Write as _; @@ -24,8 +24,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[derive(Default)] @@ -62,12 +62,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_buffer = [0; 4096]; diff --git a/examples/stm32f4/src/bin/eth.rs b/examples/stm32f4/src/bin/eth.rs index 9388c64bf..baed96449 100644 --- a/examples/stm32f4/src/bin/eth.rs +++ b/examples/stm32f4/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -24,8 +24,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -88,12 +88,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -105,7 +104,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 4096]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32f4/src/bin/eth_w5500.rs b/examples/stm32f4/src/bin/eth_w5500.rs index 5c3c6c3ba..6e6bef08c 100644 --- a/examples/stm32f4/src/bin/eth_w5500.rs +++ b/examples/stm32f4/src/bin/eth_w5500.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::{Device, Runner, State}; use embassy_stm32::exti::ExtiInput; @@ -31,8 +31,8 @@ async fn ethernet_task(runner: Runner<'static, W5500, EthernetSPI, ExtiInput<'st } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -92,12 +92,11 @@ async fn main(spawner: Spawner) -> ! { // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index 94e51c338..a9504ec04 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::rng::{self, Rng}; use embassy_stm32::time::Hertz; use embassy_stm32::usb::Driver; @@ -31,8 +31,8 @@ async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } bind_interrupts!(struct Irqs { @@ -144,11 +144,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 2fd10c8fb..1f1eadf37 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -89,12 +89,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -106,7 +105,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 4096]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs index 65cfad8c9..eee1632f5 100644 --- a/examples/stm32h5/src/bin/eth.rs +++ b/examples/stm32h5/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -28,8 +28,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -92,12 +92,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -109,7 +108,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 1024]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index b2f8ed91e..ec3f2c000 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -24,8 +24,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -91,12 +91,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -108,7 +107,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 1024]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 274c24ab1..24983ca85 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -91,12 +91,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -104,7 +103,7 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); - let client = TcpClient::new(&stack, &state); + let client = TcpClient::new(stack, &state); loop { // You need to start a server on the host machine, for example: `nc -l 8000` diff --git a/examples/stm32h7/src/bin/eth_client_mii.rs b/examples/stm32h7/src/bin/eth_client_mii.rs index aa6544f41..768d85993 100644 --- a/examples/stm32h7/src/bin/eth_client_mii.rs +++ b/examples/stm32h7/src/bin/eth_client_mii.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -97,12 +97,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -110,7 +109,7 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); - let client = TcpClient::new(&stack, &state); + let client = TcpClient::new(stack, &state); loop { // You need to start a server on the host machine, for example: `nc -l 8000` diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index bd633cecb..be4270ada 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -206,12 +206,11 @@ async fn main(spawner: Spawner) { }; // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); let cfg = wait_for_config(stack).await; let local_addr = cfg.address.address(); @@ -274,7 +273,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config; @@ -323,8 +322,8 @@ async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } // same panicking *behavior* as `panic-probe` but doesn't print a panic message diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index d02bac91d..095d50c73 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::rng::Rng; use embassy_stm32::usb::Driver; use embassy_stm32::{bind_interrupts, peripherals, rng, usb, Config}; @@ -36,8 +36,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -121,11 +121,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs index 304754c0e..ed58627f1 100644 --- a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs +++ b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs @@ -6,7 +6,7 @@ teleprobe_meta::timeout!(120); use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_enc28j60::Enc28j60; use embassy_nrf::gpio::{Level, Output, OutputDrive}; use embassy_nrf::rng::Rng; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type MyDriver = Enc28j60, Output<'static>, Delay>, Output<'static>>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -65,11 +65,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs index 6632442f1..34fb8103b 100644 --- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs +++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs @@ -6,7 +6,7 @@ teleprobe_meta::timeout!(120); use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; @@ -40,8 +40,8 @@ async fn wifi_task( type MyDriver = hosted::NetDriver<'static>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -86,16 +86,15 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/perf-client/src/lib.rs b/tests/perf-client/src/lib.rs index 54762379a..4bd9e5674 100644 --- a/tests/perf-client/src/lib.rs +++ b/tests/perf-client/src/lib.rs @@ -2,7 +2,6 @@ use defmt::{assert, *}; use embassy_futures::join::join; -use embassy_net::driver::Driver; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack}; use embassy_time::{with_timeout, Duration, Timer}; @@ -13,7 +12,7 @@ pub struct Expected { pub updown_kbps: usize, } -pub async fn run(stack: &Stack, expected: Expected) { +pub async fn run(stack: Stack<'_>, expected: Expected) { info!("Waiting for DHCP up..."); while stack.config_v4().is_none() { Timer::after_millis(100).await; @@ -38,7 +37,7 @@ const DOWNLOAD_PORT: u16 = 4321; const UPLOAD_PORT: u16 = 4322; const UPLOAD_DOWNLOAD_PORT: u16 = 4323; -async fn test_download(stack: &Stack) -> usize { +async fn test_download(stack: Stack<'_>) -> usize { info!("Testing download..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; @@ -78,7 +77,7 @@ async fn test_download(stack: &Stack) -> usize { kbps } -async fn test_upload(stack: &Stack) -> usize { +async fn test_upload(stack: Stack<'_>) -> usize { info!("Testing upload..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; @@ -118,7 +117,7 @@ async fn test_upload(stack: &Stack) -> usize { kbps } -async fn test_upload_download(stack: &Stack) -> usize { +async fn test_upload_download(stack: Stack<'_>) -> usize { info!("Testing upload+download..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index 11c8aa58c..30e4afb07 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -6,7 +6,7 @@ use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::{panic, *}; use embassy_executor::Spawner; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; @@ -30,8 +30,8 @@ async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stati } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -70,16 +70,15 @@ async fn main(spawner: Spawner) { let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( net_device, Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { match control diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs index f15f33743..ae2adfa55 100644 --- a/tests/rp/src/bin/ethernet_w5100s_perf.rs +++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -5,7 +5,7 @@ teleprobe_meta::timeout!(120); use defmt::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_wiznet::chip::W5100S; use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; @@ -32,8 +32,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -67,17 +67,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/stm32/src/bin/eth.rs b/tests/stm32/src/bin/eth.rs index 9da514881..bf1922dde 100644 --- a/tests/stm32/src/bin/eth.rs +++ b/tests/stm32/src/bin/eth.rs @@ -6,7 +6,7 @@ mod common; use common::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -32,8 +32,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -99,12 +99,11 @@ async fn main(spawner: Spawner) { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, From 73aa40a9b9ab13655aa39969494dd377f57b699e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 16 Sep 2024 21:27:34 +0200 Subject: [PATCH 096/127] Disable nrf52840 hil tests. --- ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index 5e8bddb8c..50331bdba 100755 --- a/ci.sh +++ b/ci.sh @@ -290,8 +290,9 @@ cargo batch \ $BUILD_EXTRA -# temporarily disabled, bluepill board got bricked +# temporarily disabled, these boards are dead. rm -rf out/tests/stm32f103c8 +rm -rf out/tests/nrf52840-dk rm out/tests/stm32wb55rg/wpan_mac rm out/tests/stm32wb55rg/wpan_ble From 6d89f2729ab7a6ee3dd78ccaef1b16677b5a9eee Mon Sep 17 00:00:00 2001 From: kingofpayne <43875454+kingofpayne@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:07:56 +0200 Subject: [PATCH 097/127] boot: flash-erase-zero (#3344) Allow compatibility with devices whose flash erase set bytes to 0x00 instead of 0xFF, using a new flash-erase-zero feature. See issue #3342. --- docs/pages/bootloader.adoc | 2 ++ embassy-boot/Cargo.toml | 1 + embassy-boot/src/lib.rs | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/docs/pages/bootloader.adoc b/docs/pages/bootloader.adoc index 3b0cdb182..d8d50040b 100644 --- a/docs/pages/bootloader.adoc +++ b/docs/pages/bootloader.adoc @@ -19,6 +19,8 @@ The bootloader supports In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work. +STM32L0x1 devices require the `flash-erase-zero` feature to be enabled. + == Design image::bootloader_flash.png[Bootloader flash layout] diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml index d27fe763e..79ba6456a 100644 --- a/embassy-boot/Cargo.toml +++ b/embassy-boot/Cargo.toml @@ -47,6 +47,7 @@ ed25519-dalek = { version = "2", default-features = false, features = ["std", "r [features] ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ed25519-salty = ["dep:salty", "_verify"] +flash-erase-zero = [] #Internal features _verify = [] diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs index b4f03e01e..8849055e8 100644 --- a/embassy-boot/src/lib.rs +++ b/embassy-boot/src/lib.rs @@ -14,7 +14,11 @@ mod test_flash; // The expected value of the flash after an erase // TODO: Use the value provided by NorFlash when available +#[cfg(not(feature = "flash-erase-zero"))] pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +#[cfg(feature = "flash-erase-zero")] +pub(crate) const STATE_ERASE_VALUE: u8 = 0x00; + pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; pub use firmware_updater::{ BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, From 0bfc98a3e526075ad14517589e4879d14f50ad12 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Tue, 17 Sep 2024 19:41:58 +0200 Subject: [PATCH 098/127] rp: Add PIO example for one-wire temperature sensor --- examples/rp/src/bin/pio_onewire.rs | 155 +++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 examples/rp/src/bin/pio_onewire.rs diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs new file mode 100644 index 000000000..5076101ec --- /dev/null +++ b/examples/rp/src/bin/pio_onewire.rs @@ -0,0 +1,155 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .wrap_target + again: + pull block + mov x, osr + jmp !x, read + write: + set pindirs, 1 + set pins, 0 + loop1: + jmp x--,loop1 + set pindirs, 0 [31] + wait 1 pin 0 [31] + pull block + mov x, osr + bytes1: + pull block + set y, 7 + set pindirs, 1 + bit1: + set pins, 0 [1] + out pins,1 [31] + set pins, 1 [20] + jmp y--,bit1 + jmp x--,bytes1 + set pindirs, 0 [31] + jmp again + read: + pull block + mov x, osr + bytes2: + set y, 7 + bit2: + set pindirs, 1 + set pins, 0 [1] + set pindirs, 0 [5] + in pins,1 [10] + jmp y--,bit2 + jmp x--,bytes2 + .wrap + "#, + ); + + let pin = common.make_pio_pin(pin); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + cfg.set_out_pins(&[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + cfg.shift_in = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.clock_divider = 255_u8.into(); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Write bytes over the wire + async fn write_bytes(&mut self, bytes: &[u8]) { + self.sm.tx().wait_push(250).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes { + self.sm.tx().wait_push(*b as u32).await; + } + } + + /// Read bytes from the wire + async fn read_bytes(&mut self, bytes: &mut [u8]) { + self.sm.tx().wait_push(0).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes.iter_mut() { + *b = (self.sm.rx().wait_pull().await >> 24) as u8; + } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} From 2bc49763c6c36026feeaf681078d06106b73e0b0 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 14 Sep 2024 14:57:10 +0200 Subject: [PATCH 099/127] TRNG support for 235x --- embassy-rp/src/lib.rs | 4 + embassy-rp/src/trng.rs | 405 ++++++++++++++++++++++++++++++++++ examples/rp23/src/bin/trng.rs | 64 ++++++ 3 files changed, 473 insertions(+) create mode 100644 embassy-rp/src/trng.rs create mode 100644 examples/rp23/src/bin/trng.rs diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index c357c14c2..d402cf793 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -42,6 +42,8 @@ pub mod rtc; pub mod spi; #[cfg(feature = "time-driver")] pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; pub mod uart; pub mod usb; pub mod watchdog; @@ -402,6 +404,8 @@ embassy_hal_internal::peripherals! { WATCHDOG, BOOTSEL, + + TRNG } #[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] diff --git a/embassy-rp/src/trng.rs b/embassy-rp/src/trng.rs new file mode 100644 index 000000000..9f2f33c4b --- /dev/null +++ b/embassy-rp/src/trng.rs @@ -0,0 +1,405 @@ +//! True Random Number Generator (TRNG) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::Not; +use core::task::Poll; + +use embassy_hal_internal::Peripheral; +use embassy_sync::waitqueue::AtomicWaker; +use rand_core::Error; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::peripherals::TRNG; +use crate::{interrupt, pac}; + +trait SealedInstance { + fn regs() -> pac::trng::Trng; + fn waker() -> &'static AtomicWaker; +} + +/// TRNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +impl SealedInstance for TRNG { + fn regs() -> rp_pac::trng::Trng { + pac::TRNG + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } +} + +impl Instance for TRNG { + type Interrupt = interrupt::typelevel::TRNG_IRQ; +} + +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +/// TRNG ROSC Inverter chain length options. +pub enum InverterChainLength { + None = 0, + One, + Two, + Three, + Four, +} + +impl From for u8 { + fn from(value: InverterChainLength) -> Self { + value as u8 + } +} + +/// Configuration for the TRNG. +/// +/// - Three built in entropy checks +/// - ROSC frequency controlled by selecting one of ROSC chain lengths +/// - Sample period in terms of system clock ticks +/// +/// +/// Default configuration is based on the following from documentation: +/// +/// ---- +/// +/// RP2350 Datasheet 12.12.2 +/// +/// ... +/// +/// When configuring the TRNG block, consider the following principles: +/// ā€¢ As average generation time increases, result quality increases and failed entropy checks decrease. +/// ā€¢ A low sample count decreases average generation time, but increases the chance of NIST test-failing results and +/// failed entropy checks. +/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or +/// 1 and sample count settings of 20-25. +/// +/// --- +/// +/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly +/// by setting the sample period to 0. Random data collected this way is then passed through +/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!). +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Bypass TRNG autocorrelation test + pub disable_autocorrelation_test: bool, + /// Bypass CRNGT test + pub disable_crngt_test: bool, + /// When set, the Von-Neuman balancer is bypassed (including the + /// 32 consecutive bits test) + pub disable_von_neumann_balancer: bool, + /// Sets the number of rng_clk cycles between two consecutive + /// ring oscillator samples. + /// Note: If the von Neumann decorrelator is bypassed, the minimum value for + /// sample counter must not be less than seventeen + pub sample_count: u32, + /// Selects the number of inverters (out of four possible + /// selections) in the ring oscillator (the entropy source). Higher values select + /// longer inverter chain lengths. + pub inverter_chain_length: InverterChainLength, +} + +impl Default for Config { + fn default() -> Self { + Config { + disable_autocorrelation_test: true, + disable_crngt_test: true, + disable_von_neumann_balancer: true, + sample_count: 25, + inverter_chain_length: InverterChainLength::One, + } + } +} + +/// True Random Number Generator Driver for RP2350 +/// +/// This driver provides async and blocking options. +/// +/// See [Config] for configuration details. +/// +/// Usage example: +/// ```no_run +/// use embassy_executor::Spawner; +/// use embassy_rp::trng::Trng; +/// use embassy_rp::peripherals::TRNG; +/// use embassy_rp::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// TRNG_IRQ => embassy_rp::trng::InterruptHandler; +/// }); +/// +/// #[embassy_executor::main] +/// async fn main(spawner: Spawner) { +/// let peripherals = embassy_rp::init(Default::default()); +/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); +/// +/// let mut randomness = [0u8; 58]; +/// loop { +/// trng.fill_bytes(&mut randomness).await; +/// assert_ne!(randomness, [0u8; 58]); +/// } +///} +/// ``` +pub struct Trng<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, +} + +/// 12.12.1. Overview +/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of +/// periodic samples from the TRNG blockā€™s internal Ring Oscillator (ROSC). +const TRNG_BLOCK_SIZE_BITS: usize = 192; +const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8; + +impl<'d, T: Instance> Trng<'d, T> { + /// Create a new TRNG driver. + pub fn new( + _trng: impl Peripheral

+ 'd, + _irq: impl Binding> + 'd, + config: Config, + ) -> Self { + let regs = T::regs(); + + regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false)); + + let trng_config_register = regs.trng_config(); + trng_config_register.write(|w| { + w.set_rnd_src_sel(config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(config.disable_crngt_test); + w.set_vnc_bypass(config.disable_von_neumann_balancer) + }); + + Trng { phantom: PhantomData } + } + + fn start_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + // Enable TRNG ROSC + source_enable_register.write(|w| w.set_rnd_src_en(true)); + } + + fn stop_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + source_enable_register.write(|w| w.set_rnd_src_en(false)); + let reset_bits_counter_register = regs.rst_bits_counter(); + reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true)); + } + + fn enable_irq(&self) { + unsafe { T::Interrupt::enable() } + } + + fn disable_irq(&self) { + T::Interrupt::disable(); + } + + fn blocking_wait_for_successful_generation(&self) { + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let mut success = false; + while success.not() { + while trng_busy_register.read().trng_busy() {} + if trng_valid_register.read().ehr_valid().not() { + if regs.rng_isr().read().autocorr_err() { + regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true)); + } else { + panic!("RNG not busy, but ehr is not valid!") + } + } else { + success = true + } + } + } + + fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + let regs = T::regs(); + let ehr_data_regs = [ + regs.ehr_data0(), + regs.ehr_data1(), + regs.ehr_data2(), + regs.ehr_data3(), + regs.ehr_data4(), + regs.ehr_data5(), + ]; + + for (i, reg) in ehr_data_regs.iter().enumerate() { + buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes()); + } + } + + fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + self.blocking_wait_for_successful_generation(); + self.read_ehr_registers_into_array(buffer); + } + + /// Fill the buffer with random bytes, async version. + pub async fn fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + + self.start_rng(); + self.enable_irq(); + + let mut bytes_transferred = 0usize; + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let waker = T::waker(); + + let destination_length = destination.len(); + + poll_fn(|context| { + waker.register(context.waker()); + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + if trng_busy_register.read().trng_busy() { + Poll::Pending + } else { + if trng_valid_register.read().ehr_valid().not() { + panic!("RNG not busy, but ehr is not valid!") + } + self.read_ehr_registers_into_array(&mut buffer); + let remaining = destination_length - bytes_transferred; + if remaining > TRNG_BLOCK_SIZE_BYTES { + destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES] + .copy_from_slice(&buffer); + bytes_transferred += TRNG_BLOCK_SIZE_BYTES + } else { + destination[bytes_transferred..bytes_transferred + remaining] + .copy_from_slice(&buffer[0..remaining]); + bytes_transferred += remaining + } + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + }) + .await + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + self.start_rng(); + + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) { + self.blocking_wait_for_successful_generation(); + self.blocking_read_ehr_registers_into_array(&mut buffer); + chunk.copy_from_slice(&buffer[..chunk.len()]) + } + self.stop_rng() + } + + /// Return a random u32, blocking. + pub fn blocking_next_u32(&mut self) -> u32 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = regs.ehr_data5().read(); + self.stop_rng(); + result + } + + /// Return a random u64, blocking. + pub fn blocking_next_u64(&mut self) -> u64 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + + let low = regs.ehr_data4().read() as u64; + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = (regs.ehr_data5().read() as u64) << 32 | low; + self.stop_rng(); + result + } +} + +impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} +/// TRNG interrupt handler. +pub struct InterruptHandler { + _trng: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.rng_isr().read(); + // Clear ehr bit + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + if isr.ehr_valid() { + T::waker().wake(); + } else { + // 12.12.5. List of Registers + // ... + // TRNG: RNG_ISR Register + // ... + // AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row. + // When set, RNG ceases functioning until next reset + if isr.autocorr_err() { + warn!("TRNG Autocorrect error! Resetting TRNG"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + } + } + } +} diff --git a/examples/rp23/src/bin/trng.rs b/examples/rp23/src/bin/trng.rs new file mode 100644 index 000000000..e146baa2e --- /dev/null +++ b/examples/rp23/src/bin/trng.rs @@ -0,0 +1,64 @@ +//! This example shows TRNG usage + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::TRNG; +use embassy_rp::trng::Trng; +use embassy_time::Timer; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + TRNG_IRQ => embassy_rp::trng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_rp::init(Default::default()); + + // Initialize the TRNG with default configuration + let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); + // A buffer to collect random bytes in. + let mut randomness = [0u8; 58]; + + let mut led = Output::new(peripherals.PIN_25, Level::Low); + + loop { + trng.fill_bytes(&mut randomness).await; + info!("Random bytes async {}", &randomness); + trng.blocking_fill_bytes(&mut randomness); + info!("Random bytes blocking {}", &randomness); + let random_u32 = trng.next_u32(); + let random_u64 = trng.next_u64(); + info!("Random u32 {} u64 {}", random_u32, random_u64); + // Random number of blinks between 0 and 31 + let blinks = random_u32 % 32; + for _ in 0..blinks { + led.set_high(); + Timer::after_millis(20).await; + led.set_low(); + Timer::after_millis(20).await; + } + Timer::after_millis(1000).await; + } +} From a406a01459a5c759c044a181ba992fb2bc9e1150 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 18 Sep 2024 21:24:35 +0200 Subject: [PATCH 100/127] net-esp-hosted: remove useless fn init. --- embassy-net-esp-hosted/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index c78578bf1..f05e2a70a 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs @@ -137,7 +137,7 @@ where let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); let state_ch = ch_runner.state_runner(); - let mut runner = Runner { + let runner = Runner { ch: ch_runner, state_ch, shared: &state.shared, @@ -148,7 +148,6 @@ where spi, heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, }; - runner.init().await; (device, Control::new(state_ch, &state.shared), runner) } @@ -174,8 +173,6 @@ where IN: InputPin + Wait, OUT: OutputPin, { - async fn init(&mut self) {} - /// Run the packet processing. pub async fn run(mut self) -> ! { debug!("resetting..."); From 60f93b42e262dc30bf7bd3767b1912a92f9e0f43 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 18 Sep 2024 21:24:50 +0200 Subject: [PATCH 101/127] net-esp-hosted: set wpa3_supported=true. I've noticed wpa3 still works without this flag, so I'm not sure what this does tbh... --- embassy-net-esp-hosted/src/control.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index c8cea8503..b1838a425 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs @@ -120,7 +120,7 @@ impl<'a> Control<'a> { pwd: unwrap!(String::try_from(password)), bssid: String::new(), listen_interval: 3, - is_wpa3_supported: false, + is_wpa3_supported: true, }; ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); self.state_ch.set_link_state(LinkState::Up); From bee53af36a91d984558cd22dd96195537fd61fd6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 18 Sep 2024 21:37:42 +0200 Subject: [PATCH 102/127] net: add all combinations of wait methods for link/config up/down. --- embassy-net/src/lib.rs | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index ef53fb905..a7b7efa87 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -260,7 +260,10 @@ pub struct Stack<'d> { pub(crate) struct Inner { pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. pub(crate) iface: Interface, + /// Waker used for triggering polls. pub(crate) waker: WakerRegistration, + /// Waker used for waiting for link up or config up. + state_waker: WakerRegistration, hardware_address: HardwareAddress, next_local_port: u16, link_up: bool, @@ -270,7 +273,6 @@ pub(crate) struct Inner { static_v6: Option, #[cfg(feature = "dhcpv4")] dhcp_socket: Option, - config_waker: WakerRegistration, #[cfg(feature = "dns")] dns_socket: SocketHandle, #[cfg(feature = "dns")] @@ -326,6 +328,7 @@ pub fn new<'d, D: Driver, const SOCK: usize>( sockets, iface, waker: WakerRegistration::new(), + state_waker: WakerRegistration::new(), next_local_port, hardware_address, link_up: false, @@ -335,7 +338,6 @@ pub fn new<'d, D: Driver, const SOCK: usize>( static_v6: None, #[cfg(feature = "dhcpv4")] dhcp_socket: None, - config_waker: WakerRegistration::new(), #[cfg(feature = "dns")] dns_socket, #[cfg(feature = "dns")] @@ -421,10 +423,20 @@ impl<'d> Stack<'d> { v4_up || v6_up } + /// Wait for the network device to obtain a link signal. + pub async fn wait_link_up(&self) { + self.wait(|| self.is_link_up()).await + } + + /// Wait for the network device to lose link signal. + pub async fn wait_link_down(&self) { + self.wait(|| !self.is_link_up()).await + } + /// Wait for the network stack to obtain a valid IP configuration. /// /// ## Notes: - /// - Ensure [`Stack::run`] has been called before using this function. + /// - Ensure [`Runner::run`] has been started before using this function. /// /// - This function may never return (e.g. if no configuration is obtained through DHCP). /// The caller is supposed to handle a timeout for this case. @@ -451,13 +463,17 @@ impl<'d> Stack<'d> { /// // ... /// ``` pub async fn wait_config_up(&self) { - // If the config is up already, we can return immediately. - if self.is_config_up() { - return; - } + self.wait(|| self.is_config_up()).await + } - poll_fn(|cx| { - if self.is_config_up() { + /// Wait for the network stack to lose a valid IP configuration. + pub async fn wait_config_down(&self) { + self.wait(|| !self.is_config_up()).await + } + + fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future + 'a { + poll_fn(move |cx| { + if predicate() { Poll::Ready(()) } else { // If the config is not up, we register a waker that is woken up @@ -465,13 +481,12 @@ impl<'d> Stack<'d> { trace!("Waiting for config up"); self.with_mut(|i| { - i.config_waker.register(cx.waker()); + i.state_waker.register(cx.waker()); }); Poll::Pending } }) - .await; } /// Get the current IPv4 configuration. @@ -775,7 +790,7 @@ impl Inner { .update_servers(&dns_servers[..count]); } - self.config_waker.wake(); + self.state_waker.wake(); } fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { @@ -813,6 +828,7 @@ impl Inner { // Print when changed if old_link_up != self.link_up { info!("link_up = {:?}", self.link_up); + self.state_waker.wake(); } #[allow(unused_mut)] From b1897c58fa617dbab02b39e7f5e399ed1c9d54b4 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 18 Sep 2024 16:14:53 +0200 Subject: [PATCH 103/127] Add revert state in embassy-boot The revert state signals that a firmware revert has taken place, allowing the application to know if a firmware update attempt was reverted. --- embassy-boot/src/boot_loader.rs | 6 ++++-- embassy-boot/src/firmware_updater/asynch.rs | 3 ++- embassy-boot/src/firmware_updater/blocking.rs | 3 ++- embassy-boot/src/lib.rs | 6 ++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index 61d61b96e..5bffdc5ea 100644 --- a/embassy-boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; +use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -276,7 +276,7 @@ impl BootLoader BootLoader FirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state().await? == State::Boot { + let state = self.get_state().await?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index d3c723456..5f64b4be9 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -324,7 +324,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { + let state = self.get_state()?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs index 8849055e8..7d5cc58f9 100644 --- a/embassy-boot/src/lib.rs +++ b/embassy-boot/src/lib.rs @@ -25,6 +25,7 @@ pub use firmware_updater::{ FirmwareUpdaterError, }; +pub(crate) const REVERT_MAGIC: u8 = 0xC0; pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; @@ -37,6 +38,8 @@ pub enum State { Boot, /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, + /// Bootloader has reverted the active partition with the dfu partition and will attempt boot. + Revert, /// Application has received a request to reboot into DFU mode to apply an update. DfuDetach, } @@ -157,6 +160,9 @@ mod tests { // Running again should cause a revert assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + // Next time we know it was reverted + assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap()); + let mut read_buf = [0; FIRMWARE_SIZE]; flash.active().read(0, &mut read_buf).unwrap(); assert_eq!(ORIGINAL, read_buf); From ab0a227e4c02137bc3a621907d17ede0ace4cb1d Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 19 Sep 2024 09:15:08 +0200 Subject: [PATCH 104/127] Ensure bootloader state is parsed correctly --- embassy-boot/src/firmware_updater/asynch.rs | 7 +------ embassy-boot/src/firmware_updater/blocking.rs | 9 +-------- embassy-boot/src/lib.rs | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs index b23857e2f..d9d15b004 100644 --- a/embassy-boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/src/firmware_updater/asynch.rs @@ -304,12 +304,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { /// `mark_booted`. pub async fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned).await?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index 5f64b4be9..08062b0d0 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -339,14 +339,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { /// `mark_booted`. pub fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned)?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs index 7d5cc58f9..e2c4cf771 100644 --- a/embassy-boot/src/lib.rs +++ b/embassy-boot/src/lib.rs @@ -44,6 +44,24 @@ pub enum State { DfuDetach, } +impl From for State +where + T: AsRef<[u8]>, +{ + fn from(magic: T) -> State { + let magic = magic.as_ref(); + if !magic.iter().any(|&b| b != SWAP_MAGIC) { + State::Swap + } else if !magic.iter().any(|&b| b != REVERT_MAGIC) { + State::Revert + } else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) { + State::DfuDetach + } else { + State::Boot + } + } +} + /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. #[repr(align(32))] pub struct AlignedBuffer(pub [u8; N]); From df23a77bfc3c8b5d8ab6adbd12842fa4cfe3675d Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 19 Sep 2024 09:15:35 +0200 Subject: [PATCH 105/127] Add led to example to demonstrate revert state detection --- examples/boot/application/nrf/src/bin/a.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 851a3d721..60cf3cd1a 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -8,6 +8,7 @@ use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::nvmc::Nvmc; use embassy_nrf::wdt::{self, Watchdog}; +use embassy_boot::State; use embassy_sync::mutex::Mutex; use panic_reset as _; @@ -22,6 +23,7 @@ async fn main(_spawner: Spawner) { let mut button = Input::new(p.P0_11, Pull::Up); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + let mut led_reverted = Output::new(p.P0_14, Level::High, OutputDrive::Standard); //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); //let mut button = Input::new(p.P1_02, Pull::Up); @@ -53,6 +55,13 @@ async fn main(_spawner: Spawner) { let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc); let mut magic = [0; 4]; let mut updater = FirmwareUpdater::new(config, &mut magic); + let state = updater.get_state().await.unwrap(); + if state == State::Revert { + led_reverted.set_low(); + } else { + led_reverted.set_high(); + } + loop { led.set_low(); button.wait_for_any_edge().await; From 4e1efd93fd4dc8dd692daf419d901ae22413e091 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 19 Sep 2024 09:15:55 +0200 Subject: [PATCH 106/127] Fix defmt support for example boot app --- examples/boot/application/nrf/build.rs | 3 +++ examples/boot/application/nrf/src/bin/a.rs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/boot/application/nrf/build.rs b/examples/boot/application/nrf/build.rs index cd1a264c4..e1da69328 100644 --- a/examples/boot/application/nrf/build.rs +++ b/examples/boot/application/nrf/build.rs @@ -31,4 +31,7 @@ fn main() { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } } diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 60cf3cd1a..2c1d1a7bb 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -2,13 +2,15 @@ #![no_main] #![macro_use] +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot::State; use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::nvmc::Nvmc; use embassy_nrf::wdt::{self, Watchdog}; -use embassy_boot::State; use embassy_sync::mutex::Mutex; use panic_reset as _; From 893b8d79e8bab8dae0f46a0182443df9160592b5 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Thu, 19 Sep 2024 08:17:33 -0400 Subject: [PATCH 107/127] embassy_sync/pubsub: fix PubSubBehavior visibility https://github.com/embassy-rs/embassy/pull/2969 appears to have broken direct `publish_immediate()` on `pubsub::Channel`, as it functionally made `PubSubBehavior` private and didn't delegate this method to the new (private) `SealedPubSubBehavior`. This change moves `publish_immediate`, `capacity`, and `is_full` from `SealedPubSubBehavior` to `PubSubBehavior` in order to restore them to `pub` visibility. --- embassy-sync/src/pubsub/mod.rs | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index a97eb7d5b..812302e2b 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -194,6 +194,25 @@ impl crate::pubsub::PubSubBehavior + for PubSubChannel +{ + fn publish_immediate(&self, message: T) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.publish_immediate(message) + }) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn is_full(&self) -> bool { + self.is_full() + } +} + impl SealedPubSubBehavior for PubSubChannel { @@ -246,13 +265,6 @@ impl usize { - self.capacity() - } - fn free_capacity(&self) -> usize { self.free_capacity() } @@ -286,10 +294,6 @@ impl bool { self.is_empty() } - - fn is_full(&self) -> bool { - self.is_full() - } } /// Internal state for the PubSub channel @@ -445,8 +449,6 @@ pub enum Error { MaximumPublishersReached, } -/// 'Middle level' behaviour of the pubsub channel. -/// This trait is used so that Sub and Pub can be generic over the channel. trait SealedPubSubBehavior { /// Try to get a message from the queue with the given message id. /// @@ -462,12 +464,6 @@ trait SealedPubSubBehavior { /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>; - /// Publish a message immediately - fn publish_immediate(&self, message: T); - - /// Returns the maximum number of elements the channel can hold. - fn capacity(&self) -> usize; - /// Returns the free capacity of the channel. /// /// This is equivalent to `capacity() - len()` @@ -482,9 +478,6 @@ trait SealedPubSubBehavior { /// Returns whether the channel is empty. fn is_empty(&self) -> bool; - /// Returns whether the channel is full. - fn is_full(&self) -> bool; - /// Let the channel know that a subscriber has dropped fn unregister_subscriber(&self, subscriber_next_message_id: u64); @@ -495,9 +488,16 @@ trait SealedPubSubBehavior { /// 'Middle level' behaviour of the pubsub channel. /// This trait is used so that Sub and Pub can be generic over the channel. #[allow(private_bounds)] -pub trait PubSubBehavior: SealedPubSubBehavior {} +pub trait PubSubBehavior: SealedPubSubBehavior { + /// Publish a message immediately + fn publish_immediate(&self, message: T); -impl> PubSubBehavior for C {} + /// Returns the maximum number of elements the channel can hold. + fn capacity(&self) -> usize; + + /// Returns whether the channel is full. + fn is_full(&self) -> bool; +} /// The result of the subscriber wait procedure #[derive(Debug, Clone, PartialEq, Eq)] From 907d55ea82ac09b507afdc7ccb4d5997f827a748 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 19 Sep 2024 18:14:09 +0200 Subject: [PATCH 108/127] stm32: Added request_pause to DMA, and use it for RingBufferedUartRx --- embassy-stm32/src/dma/dma_bdma.rs | 54 ++++++++++++++++++++++++- embassy-stm32/src/usart/ringbuffered.rs | 2 +- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index df041c4e9..2887536c3 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -493,6 +493,26 @@ impl AnyChannel { } } + fn request_pause(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + r.st(info.num).cr().modify(|w| { + // Disable the channel without overwriting the existing configuration + w.set_en(false); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + r.ch(info.num).cr().modify(|w| { + // Disable the channel without overwriting the existing configuration + w.set_en(false); + }); + } + } + } + fn is_running(&self) -> bool { let info = self.info(); match self.info().dma { @@ -667,12 +687,22 @@ impl<'a> Transfer<'a> { } /// Request the transfer to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether this transfer is still running. /// /// If this returns `false`, it can be because either the transfer finished, or @@ -846,13 +876,23 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } - /// Request DMA to stop. + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether DMA is still running. /// /// If this returns `false`, it can be because either the transfer finished, or @@ -977,13 +1017,23 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } - /// Request DMA to stop. + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether DMA is still running. /// /// If this returns `false`, it can be because either the transfer finished, or diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index b0652046c..bb95af966 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -120,7 +120,7 @@ impl<'d> RingBufferedUartRx<'d> { /// Stop uart background receive fn teardown_uart(&mut self) { - self.ring_buf.request_stop(); + self.ring_buf.request_pause(); let r = self.info.regs; // clear all interrupts and DMA Rx Request From 2a9cdaabaa315795722886f94fa553e8f3e3c14d Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 19 Sep 2024 18:25:08 +0200 Subject: [PATCH 109/127] stm32: Moved comment to match request_stop --- embassy-stm32/src/dma/dma_bdma.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 2887536c3..d10b5554f 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -498,15 +498,15 @@ impl AnyChannel { match self.info().dma { #[cfg(dma)] DmaInfo::Dma(r) => { + // Disable the channel without overwriting the existing configuration r.st(info.num).cr().modify(|w| { - // Disable the channel without overwriting the existing configuration w.set_en(false); }); } #[cfg(bdma)] DmaInfo::Bdma(r) => { + // Disable the channel without overwriting the existing configuration r.ch(info.num).cr().modify(|w| { - // Disable the channel without overwriting the existing configuration w.set_en(false); }); } From 4fcc8e39d6b3e486d2b94aa45d255d9c645d028a Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 19 Sep 2024 19:21:34 +0200 Subject: [PATCH 110/127] stm32: Only check errors on running RingBufferedUartRx, reduce number of small one-time functions --- embassy-stm32/src/usart/ringbuffered.rs | 49 ++++++++++--------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index bb95af966..2d9c63820 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -71,33 +71,18 @@ impl<'d> UartRx<'d, Async> { } impl<'d> RingBufferedUartRx<'d> { - /// Clear the ring buffer and start receiving in the background - pub fn start(&mut self) -> Result<(), Error> { - // Clear the ring buffer so that it is ready to receive data - self.ring_buf.clear(); - - self.setup_uart(); - - Ok(()) - } - - fn stop(&mut self, err: Error) -> Result { - self.teardown_uart(); - - Err(err) - } - /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { reconfigure(self.info, self.kernel_clock, config) } - /// Start uart background receive - fn setup_uart(&mut self) { - // fence before starting DMA. + /// Configure and start the DMA backed UART receiver + /// + /// Note: This is also done automatically by [`read()`] if required. + pub fn start_uart(&mut self) { + // Clear the buffer so that it is ready to receive data + self.ring_buf.clear(); compiler_fence(Ordering::SeqCst); - - // start the dma controller self.ring_buf.start(); let r = self.info.regs; @@ -118,8 +103,8 @@ impl<'d> RingBufferedUartRx<'d> { }); } - /// Stop uart background receive - fn teardown_uart(&mut self) { + /// Stop DMA backed UART receiver + fn stop_uart(&mut self) { self.ring_buf.request_pause(); let r = self.info.regs; @@ -153,13 +138,15 @@ impl<'d> RingBufferedUartRx<'d> { pub async fn read(&mut self, buf: &mut [u8]) -> Result { let r = self.info.regs; - // Start background receive if it was not already started + // Start DMA and Uart if it was not already started, + // otherwise check for errors in status register. + let sr = clear_idle_flag(r); if !r.cr3().read().dmar() { - self.start()?; + self.start_uart(); + } else { + check_for_errors(sr)?; } - check_for_errors(clear_idle_flag(r))?; - loop { match self.ring_buf.read(buf) { Ok((0, _)) => {} @@ -167,14 +154,16 @@ impl<'d> RingBufferedUartRx<'d> { return Ok(len); } Err(_) => { - return self.stop(Error::Overrun); + self.stop_uart(); + return Err(Error::Overrun); } } match self.wait_for_data_or_idle().await { Ok(_) => {} Err(err) => { - return self.stop(err); + self.stop_uart(); + return Err(err); } } } @@ -228,7 +217,7 @@ impl<'d> RingBufferedUartRx<'d> { impl Drop for RingBufferedUartRx<'_> { fn drop(&mut self) { - self.teardown_uart(); + self.stop_uart(); self.rx.as_ref().map(|x| x.set_as_disconnected()); self.rts.as_ref().map(|x| x.set_as_disconnected()); super::drop_tx_rx(self.info, self.state); From 3aeeeb0d784d843b521b4b8b222114ef7ba71363 Mon Sep 17 00:00:00 2001 From: Peter Krull Date: Thu, 19 Sep 2024 20:07:08 +0200 Subject: [PATCH 111/127] stm32: Start DMA before clearing, avoid panic in updater ringbuffer impl --- embassy-stm32/src/usart/ringbuffered.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 2d9c63820..75834bf37 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -81,9 +81,9 @@ impl<'d> RingBufferedUartRx<'d> { /// Note: This is also done automatically by [`read()`] if required. pub fn start_uart(&mut self) { // Clear the buffer so that it is ready to receive data - self.ring_buf.clear(); compiler_fence(Ordering::SeqCst); self.ring_buf.start(); + self.ring_buf.clear(); let r = self.info.regs; // clear all interrupts and DMA Rx Request From 5ea934d4ba6f0bc0e5e47b16f17dd8e881b528a3 Mon Sep 17 00:00:00 2001 From: Gerhard de Clercq Date: Fri, 20 Sep 2024 09:57:31 +0200 Subject: [PATCH 112/127] embassy_stm32/eth: support compliance testing This change adds the possibility to perform compliance testing with STM32 systems by directly exposing SMI when needed. Users can then use this to configure PHY registers for test modes. --- embassy-stm32/src/eth/mod.rs | 14 ++++ .../stm32f4/src/bin/eth_compliance_test.rs | 77 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 examples/stm32f4/src/bin/eth_compliance_test.rs diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index bfe8a60d6..6442176da 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -177,6 +177,20 @@ pub unsafe trait PHY { fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool; } +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + /// Directly expose the SMI interface used by the Ethernet driver. + /// + /// This can be used to for example configure special PHY registers for compliance testing. + /// + /// # Safety + /// + /// Revert any temporary PHY register changes such as to enable test modes before handing + /// the Ethernet device over to the networking stack otherwise things likely won't work. + pub unsafe fn station_management(&mut self) -> &mut impl StationManagement { + &mut self.station_management + } +} + trait SealedInstance { fn regs() -> crate::pac::eth::Eth; } diff --git a/examples/stm32f4/src/bin/eth_compliance_test.rs b/examples/stm32f4/src/bin/eth_compliance_test.rs new file mode 100644 index 000000000..5946fed79 --- /dev/null +++ b/examples/stm32f4/src/bin/eth_compliance_test.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue, StationManagement}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello Compliance World!"); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + const PHY_ADDR: u8 = 0; + static PACKETS: StaticCell> = StaticCell::new(); + let mut device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(PHY_ADDR), + mac_addr, + ); + + let sm = unsafe { device.station_management() }; + + // Just an example. Exact register settings depend on the specific PHY and test. + sm.smi_write(PHY_ADDR, 0, 0x2100); + sm.smi_write(PHY_ADDR, 11, 0xA000); + + // NB: Remember to reset the PHY after testing before starting the networking stack + + loop { + Timer::after_secs(1).await; + } +} From 3328c5d6567bb5b1df7ca26d818a91e0740e56c1 Mon Sep 17 00:00:00 2001 From: Reed Date: Sat, 21 Sep 2024 12:31:38 +1200 Subject: [PATCH 113/127] Correctly gate `time` feature of embassy-embedded-hal in embassy-stm32 --- embassy-stm32/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 3c6484c96..3f4a5e3c4 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -47,7 +47,7 @@ embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } -embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" } @@ -129,7 +129,7 @@ unstable-pac = [] #! ## Time ## Enables additional driver features that depend on embassy-time -time = ["dep:embassy-time"] +time = ["dep:embassy-time", "embassy-embedded-hal/time"] # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. From 2f60d78ea318f51ff59868c348b77cf880012198 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 15 Sep 2024 02:44:16 +0200 Subject: [PATCH 114/127] Add OTG_HS support for STM32H7R/S --- embassy-stm32/Cargo.toml | 6 +- embassy-stm32/src/rcc/h.rs | 4 +- embassy-stm32/src/usb/mod.rs | 20 +++ embassy-stm32/src/usb/otg.rs | 53 +++++++ embassy-usb-synopsys-otg/src/lib.rs | 16 +++ embassy-usb-synopsys-otg/src/otg_v1.rs | 171 +++++++++++++++++++++++ examples/stm32h7rs/src/bin/usb_serial.rs | 139 ++++++++++++++++++ 7 files changed, 405 insertions(+), 4 deletions(-) create mode 100644 examples/stm32h7rs/src/bin/usb_serial.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 3f4a5e3c4..2f7f373af 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,7 +72,8 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364" } +# stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364" } +stm32-metapac = { path = "../../stm32-data/build/stm32-metapac" } vcell = "0.1.3" nb = "1.0.0" @@ -99,7 +100,8 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364", default-features = false, features = ["metadata"] } +# stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364", default-features = false, features = ["metadata"] } +stm32-metapac = { path = "../../stm32-data/build/stm32-metapac", default-features = false, features = ["metadata"] } [features] default = ["rt"] diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index 376a0b454..27fc2b8d7 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -698,7 +698,7 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32h7rs)] clk48mohci: None, // TODO #[cfg(stm32h7rs)] - usb: None, // TODO + usb: Some(Hertz(48_000_000)), ); } @@ -769,7 +769,7 @@ fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { if num == 0 { // on PLL1, DIVP must be even for most series. // The enum value is 1 less than the divider, so check it's odd. - #[cfg(not(pwr_h7rm0468))] + #[cfg(not(any(pwr_h7rm0468, stm32h7rs)))] assert!(div.to_bits() % 2 == 1); #[cfg(pwr_h7rm0468)] assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0); diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index ce9fe0a9b..7d8c79618 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -48,6 +48,26 @@ fn common_init() { while !crate::pac::PWR.cr3().read().usb33rdy() {} } + #[cfg(stm32h7rs)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.csr2().modify(|w| { + w.set_usbregen(internal_regulator); + w.set_usb33den(true); + w.set_usbhsregen(true); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.csr2().read().usb33rdy() {} + } + #[cfg(stm32u5)] { // Enable USB power diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index e27b164e4..59b5401cc 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -97,6 +97,45 @@ impl<'d, T: Instance> Driver<'d, T> { } } + /// Initializes USB OTG peripheral with internal High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + dp: impl Peripheral

> + 'd, + dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(dp, dm); + + dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + let regs = T::regs(); + + let instance = OtgInstance { + regs, + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::InternalHighSpeed, + quirk_setup_late_cnak: quirk_setup_late_cnak(regs), + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + /// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode). /// /// # Arguments @@ -272,6 +311,19 @@ impl<'d, T: Instance> Bus<'d, T> { } }); + #[cfg(stm32h7rs)] + critical_section::with(|_| { + let rcc = crate::pac::RCC; + rcc.ahb1enr().modify(|w| { + w.set_usbphycen(true); + w.set_usb_otg_hsen(true); + }); + rcc.ahb1lpenr().modify(|w| { + w.set_usbphyclpen(true); + w.set_usb_otg_hslpen(true); + }); + }); + let r = T::regs(); let core_id = r.cid().read().0; trace!("Core id {:08x}", core_id); @@ -286,6 +338,7 @@ impl<'d, T: Instance> Bus<'d, T> { match core_id { 0x0000_1200 | 0x0000_1100 => self.inner.config_v1(), 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => self.inner.config_v2v3(), + 0x0000_5000 => self.inner.config_v5(), _ => unimplemented!("Unknown USB core id {:X}", core_id), } } diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index b145f4aa8..3ff965149 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -584,6 +584,22 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { }); } + pub fn config_v5(&mut self) { + let r = self.instance.regs; + + r.gccfg_v3().modify(|w| { + w.set_vbvaloven(true); + w.set_vbvaloval(true); + w.set_vbden(self.config.vbus_detection); + }); + + // Force B-peripheral session + r.gotgctl().modify(|w| { + w.set_vbvaloen(!self.config.vbus_detection); + w.set_bvaloval(true); + }); + } + fn init(&mut self) { let r = self.instance.regs; let phy_type = self.instance.phy_type; diff --git a/embassy-usb-synopsys-otg/src/otg_v1.rs b/embassy-usb-synopsys-otg/src/otg_v1.rs index d3abc328d..18e760fd1 100644 --- a/embassy-usb-synopsys-otg/src/otg_v1.rs +++ b/embassy-usb-synopsys-otg/src/otg_v1.rs @@ -186,6 +186,11 @@ impl Otg { pub const fn gccfg_v2(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } } + #[doc = "General core configuration register, for core_id 0x0000_5xxx"] + #[inline(always)] + pub const fn gccfg_v3(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } + } #[doc = "Core ID register"] #[inline(always)] pub const fn cid(self) -> Reg { @@ -1831,6 +1836,172 @@ pub mod regs { GccfgV2(0) } } + #[doc = "OTG general core configuration register."] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct GccfgV3(pub u32); + impl GccfgV3 { + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub const fn chgdet(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub fn set_chgdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvplus(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvplus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvminus(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvminus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub const fn sessvld(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub fn set_sessvld(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub const fn hcdpen(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub fn set_hcdpen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub const fn hcdpdeten(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub fn set_hcdpdeten(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub const fn hvdmsrcen(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub fn set_hvdmsrcen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub const fn dcden(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub fn set_dcden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub const fn pden(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub fn set_pden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub const fn vbden(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub fn set_vbden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub const fn sden(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub fn set_sden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloval(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloven(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloven(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub const fn forcehostpd(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub fn set_forcehostpd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + } + impl Default for GccfgV3 { + #[inline(always)] + fn default() -> GccfgV3 { + GccfgV3(0) + } + } #[doc = "I2C access register"] #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq)] diff --git a/examples/stm32h7rs/src/bin/usb_serial.rs b/examples/stm32h7rs/src/bin/usb_serial.rs new file mode 100644 index 000000000..5a234e898 --- /dev/null +++ b/examples/stm32h7rs/src/bin/usb_serial.rs @@ -0,0 +1,139 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV12, + mul: PllMul::MUL300, + divp: Some(PllDiv::DIV1), //600 MHz + divq: Some(PllDiv::DIV2), // 300 MHz + divr: Some(PllDiv::DIV2), // 300 MHz + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 MHz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 MHz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.voltage_scale = VoltageScale::HIGH; + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PM6, p.PM5, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} From 6d9af8304cf88dbfa3713acfef4d89ba3a95c2d8 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 15 Sep 2024 20:09:42 +0200 Subject: [PATCH 115/127] Add USBPHYC clock configuration for H7RS series --- embassy-stm32/Cargo.toml | 6 ++--- embassy-stm32/src/rcc/h.rs | 32 +++++++++++++++++++++++- embassy-stm32/src/usb/mod.rs | 10 ++++++++ embassy-stm32/src/usb/otg.rs | 2 +- embassy-usb-synopsys-otg/src/lib.rs | 29 +++++++++++++-------- examples/stm32h7rs/src/bin/usb_serial.rs | 1 + 6 files changed, 63 insertions(+), 17 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 2f7f373af..575c4f20c 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,8 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -# stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364" } -stm32-metapac = { path = "../../stm32-data/build/stm32-metapac" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-acaf04256034066bd5b3a8426224ccf3e4cb7d19" } vcell = "0.1.3" nb = "1.0.0" @@ -100,8 +99,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -# stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364", default-features = false, features = ["metadata"] } -stm32-metapac = { path = "../../stm32-data/build/stm32-metapac", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-acaf04256034066bd5b3a8426224ccf3e4cb7d19", default-features = false, features = ["metadata"] } [features] default = ["rt"] diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index 27fc2b8d7..cd1c10407 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -35,7 +35,10 @@ pub enum VoltageScale { Scale3, } #[cfg(any(stm32h7rs))] -pub use crate::pac::pwr::vals::Vos as VoltageScale; +pub use crate::pac::{ + pwr::vals::Vos as VoltageScale, + rcc::vals::{Usbphycsel, Usbrefcksel}, +}; #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { @@ -557,6 +560,27 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); + #[cfg(stm32h7rs)] + let usb_refck = match config.mux.usbphycsel { + Usbphycsel::HSE => hse, + Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Usbphycsel::PLL3_Q => pll3.q, + _ => None, + }; + #[cfg(stm32h7rs)] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select USBPHYC reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + #[cfg(stm32h7)] { RCC.d1cfgr().modify(|w| { @@ -593,6 +617,10 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre4(config.apb4_pre); w.set_ppre5(config.apb5_pre); }); + + RCC.ahbperckselr().modify(|w| { + w.set_usbrefcksel(usb_refck_sel); + }); } #[cfg(stm32h5)] { @@ -698,6 +726,8 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32h7rs)] clk48mohci: None, // TODO #[cfg(stm32h7rs)] + hse_div_2: hse.map(|clk| clk / 2u32), + #[cfg(stm32h7rs)] usb: Some(Hertz(48_000_000)), ); } diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index 7d8c79618..a473285bf 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -13,9 +13,19 @@ fn common_init() { // Check the USB clock is enabled and running at exactly 48 MHz. // frequency() will panic if not enabled let freq = T::frequency(); + + // On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally + #[cfg(stm32h7rs)] + if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) { + panic!( + "USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } // Check frequency is within the 0.25% tolerance allowed by the spec. // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user // has tight clock restrictions due to something else (like audio). + #[cfg(not(stm32h7rs))] if freq.0.abs_diff(48_000_000) > 120_000 { panic!( "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index 59b5401cc..00cafe6e4 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -554,7 +554,7 @@ fn calculate_trdt(speed: Dspd) -> u8 { match speed { Dspd::HIGH_SPEED => { // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) - if ahb_freq >= 30_000_000 { + if ahb_freq >= 30_000_000 || cfg!(stm32h7rs) { 0x9 } else { panic!("AHB frequency is too low") diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index 3ff965149..f90403936 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -584,20 +584,27 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { }); } + /// Applies configuration specific to + /// Core ID 0x0000_5000 pub fn config_v5(&mut self) { let r = self.instance.regs; + let phy_type = self.instance.phy_type; - r.gccfg_v3().modify(|w| { - w.set_vbvaloven(true); - w.set_vbvaloval(true); - w.set_vbden(self.config.vbus_detection); - }); - - // Force B-peripheral session - r.gotgctl().modify(|w| { - w.set_vbvaloen(!self.config.vbus_detection); - w.set_bvaloval(true); - }); + if phy_type == PhyType::InternalHighSpeed { + r.gccfg_v3().modify(|w| { + w.set_vbvaloven(!self.config.vbus_detection); + w.set_vbvaloval(!self.config.vbus_detection); + w.set_vbden(self.config.vbus_detection); + }); + } else { + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(!self.config.vbus_detection); + }); + r.gccfg_v3().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + } } fn init(&mut self) { diff --git a/examples/stm32h7rs/src/bin/usb_serial.rs b/examples/stm32h7rs/src/bin/usb_serial.rs index 5a234e898..6773f7843 100644 --- a/examples/stm32h7rs/src/bin/usb_serial.rs +++ b/examples/stm32h7rs/src/bin/usb_serial.rs @@ -48,6 +48,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 MHz config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 MHz config.rcc.voltage_scale = VoltageScale::HIGH; + config.rcc.mux.usbphycsel = mux::Usbphycsel::HSE; } let p = embassy_stm32::init(config); From 85b7c8957cce3fef6011e63d7cb6ff85912ccb50 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 22 Sep 2024 01:11:32 +0200 Subject: [PATCH 116/127] Add presence check for OTG_HS peripheral on STM32H7R/S series --- embassy-stm32/build.rs | 4 +++- embassy-stm32/src/rcc/h.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 19cf193d9..28c619c6b 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -55,7 +55,7 @@ fn main() { let mut singletons: Vec = Vec::new(); for p in METADATA.peripherals { if let Some(r) = &p.registers { - if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" { + if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" { // TODO: should we emit this for all peripherals? if so, we will need a list of all // possible peripherals across all chips, so that we can declare the configs // (replacing the hard-coded list of `peri_*` cfgs below) @@ -111,6 +111,8 @@ fn main() { "peri_sai4", "peri_ucpd1", "peri_ucpd2", + "peri_usb_otg_fs", + "peri_usb_otg_hs", ]); cfgs.declare_all(&["mco", "mco1", "mco2"]); diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index cd1c10407..55fe8ca9d 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -34,11 +34,10 @@ pub enum VoltageScale { Scale2, Scale3, } -#[cfg(any(stm32h7rs))] -pub use crate::pac::{ - pwr::vals::Vos as VoltageScale, - rcc::vals::{Usbphycsel, Usbrefcksel}, -}; +#[cfg(stm32h7rs)] +pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(stm32h7rs, peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::{Usbphycsel, Usbrefcksel}; #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { @@ -560,14 +559,14 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); - #[cfg(stm32h7rs)] + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] let usb_refck = match config.mux.usbphycsel { Usbphycsel::HSE => hse, Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), Usbphycsel::PLL3_Q => pll3.q, _ => None, }; - #[cfg(stm32h7rs)] + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] let usb_refck_sel = match usb_refck { Some(clk_val) => match clk_val { Hertz(16_000_000) => Usbrefcksel::MHZ16, @@ -618,6 +617,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre5(config.apb5_pre); }); + #[cfg(peri_usb_otg_hs)] RCC.ahbperckselr().modify(|w| { w.set_usbrefcksel(usb_refck_sel); }); From db31e3648500bc7f36e8ac0d8c101b88cd4b956c Mon Sep 17 00:00:00 2001 From: Shaw Drastin <168159404+showier-drastic@users.noreply.github.com> Date: Sun, 22 Sep 2024 02:05:17 +0800 Subject: [PATCH 117/127] stm32/spi: issue correct DMA word length when reading Currently, when calling read() of the SPI bus, DMA always transmits u8, which will cause hang if SPI transfer size > 8bit. Use matching word size for TX DMA instead. --- embassy-stm32/src/spi/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 20718147a..dfac4bc89 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -783,7 +783,7 @@ impl<'d> Spi<'d, Async> { let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) }; let tx_dst = self.info.regs.tx_ptr(); - let clock_byte = 0x00u8; + let clock_byte = W::default(); let tx_f = unsafe { self.tx_dma .as_mut() @@ -1195,7 +1195,7 @@ trait SealedWord { /// Word sizes usable for SPI. #[allow(private_bounds)] -pub trait Word: word::Word + SealedWord {} +pub trait Word: word::Word + SealedWord + Default {} macro_rules! impl_word { ($T:ty, $config:expr) => { From e2d2b0f36281326772678f0e903675927faaaddb Mon Sep 17 00:00:00 2001 From: Hans Josephsen Date: Sun, 22 Sep 2024 12:30:38 +0200 Subject: [PATCH 118/127] Currently the return value of `write` is broken, it never returns the previous frame even when present. This happens because a slice of length 64 is always passed to Frame::new from within the `abort_pending_mailbox` function, causing `Frame::new` to return None. The fix is to take a subslice of length `data_length`. --- .gitignore | 1 + embassy-stm32/src/can/fd/peripheral.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1c221e876..a0b5d6a70 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Cargo.lock third_party /Cargo.toml out/ +.zed diff --git a/embassy-stm32/src/can/fd/peripheral.rs b/embassy-stm32/src/can/fd/peripheral.rs index 07e3dddad..1c7abfcb2 100644 --- a/embassy-stm32/src/can/fd/peripheral.rs +++ b/embassy-stm32/src/can/fd/peripheral.rs @@ -200,7 +200,7 @@ impl Registers { if header_reg.rtr().bit() { F::new_remote(id, len as usize) } else { - F::new(id, &data) + F::new(id, &data[0..(len as usize)]) } } else { // Abort request failed because the frame was already sent (or being sent) on From f2f96a731c1c08045a210083d307bc63814ae6bf Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 23 Sep 2024 00:32:17 +0200 Subject: [PATCH 119/127] stm32/gpdma: ensure bndt in bytes doesn't overflow. --- embassy-stm32/src/dma/gpdma.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index f9d66ca86..792ddc4e8 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -216,7 +216,10 @@ impl<'a> Transfer<'a> { data_size: WordSize, _options: TransferOptions, ) -> Self { - assert!(mem_len > 0 && mem_len <= 0xFFFF); + // BNDT is specified as bytes, not as number of transfers. + let Ok(bndt) = (mem_len * data_size.bytes()).try_into() else { + panic!("DMA transfers may not be larger than 65535 bytes."); + }; let info = channel.info(); let ch = info.dma.ch(info.num); @@ -226,9 +229,6 @@ impl<'a> Transfer<'a> { let this = Self { channel }; - #[cfg(dmamux)] - super::dmamux::configure_dmamux(&*this.channel, request); - ch.cr().write(|w| w.set_reset(true)); ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs ch.llr().write(|_| {}); // no linked list @@ -245,10 +245,7 @@ impl<'a> Transfer<'a> { }); w.set_reqsel(request); }); - ch.br1().write(|w| { - // BNDT is specified as bytes, not as number of transfers. - w.set_bndt((mem_len * data_size.bytes()) as u16) - }); + ch.br1().write(|w| w.set_bndt(bndt)); match dir { Dir::MemoryToPeripheral => { From 59dcffbc6021ab1aaef1dd546aa7e707b438e45f Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 23 Sep 2024 01:32:55 +0200 Subject: [PATCH 120/127] stm32/gpdma: clear tr3 just in case. --- embassy-stm32/src/dma/gpdma.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index 792ddc4e8..a877bb8d4 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -245,6 +245,7 @@ impl<'a> Transfer<'a> { }); w.set_reqsel(request); }); + ch.tr3().write(|_| {}); // no address offsets. ch.br1().write(|w| w.set_bndt(bndt)); match dir { From 68b783aedfd7c85505d5961ad3030719676210d1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 23 Sep 2024 01:59:05 +0200 Subject: [PATCH 121/127] stm32/spi: fix hang/corruption of word sizes other than 8bit. --- embassy-stm32/src/spi/mod.rs | 72 +++++++++++++----------------------- 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index dfac4bc89..d034c028e 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -311,51 +311,29 @@ impl<'d, M: PeriMode> Spi<'d, M> { } } + /// Set SPI word size. Disables SPI if needed, you have to enable it back yourself. fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + #[cfg(any(spi_v1, spi_f1))] - { - self.info.regs.cr1().modify(|reg| { - reg.set_spe(false); - reg.set_dff(word_size) - }); - self.info.regs.cr1().modify(|reg| { - reg.set_spe(true); - }); - } + self.info.regs.cr1().modify(|reg| { + reg.set_dff(word_size); + }); #[cfg(spi_v2)] - { - self.info.regs.cr1().modify(|w| { - w.set_spe(false); - }); - self.info.regs.cr2().modify(|w| { - w.set_frxth(word_size.1); - w.set_ds(word_size.0); - }); - self.info.regs.cr1().modify(|w| { - w.set_spe(true); - }); - } + self.info.regs.cr2().modify(|w| { + w.set_frxth(word_size.1); + w.set_ds(word_size.0); + }); #[cfg(any(spi_v3, spi_v4, spi_v5))] - { - self.info.regs.cr1().modify(|w| { - w.set_csusp(true); - }); - while self.info.regs.sr().read().eot() {} - self.info.regs.cr1().modify(|w| { - w.set_spe(false); - }); - self.info.regs.cfg1().modify(|w| { - w.set_dsize(word_size); - }); - self.info.regs.cr1().modify(|w| { - w.set_csusp(false); - w.set_spe(true); - }); - } + self.info.regs.cfg1().modify(|w| { + w.set_dsize(word_size); + }); self.current_word_size = word_size; } @@ -365,9 +343,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter() { // this cannot use `transfer_word` because on SPIv2 and higher, // the SPI RX state machine hangs if no physical pin is connected to the SCK AF. @@ -402,9 +380,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(self.info.regs, W::default())?; } @@ -418,9 +396,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(self.info.regs, *word)?; } @@ -437,9 +415,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); let len = read.len().max(write.len()); for i in 0..len { let wb = write.get(i).copied().unwrap_or_default(); @@ -648,10 +626,10 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); let tx_dst = self.info.regs.tx_ptr(); let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) }; @@ -685,6 +663,8 @@ impl<'d> Spi<'d, Async> { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + let comm = regs.cfg2().modify(|w| { let prev = w.comm(); w.set_comm(vals::Comm::RECEIVER); @@ -707,7 +687,6 @@ impl<'d> Spi<'d, Async> { let rx_src = regs.rx_ptr(); for mut chunk in data.chunks_mut(u16::max_value().into()) { - self.set_word_size(W::CONFIG); set_rxdmaen(regs, true); let tsize = chunk.len(); @@ -765,12 +744,12 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); - self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + // SPIv3 clears rxfifo on SPE=0 #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(self.info.regs); @@ -813,11 +792,12 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + // SPIv3 clears rxfifo on SPE=0 #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(self.info.regs); From fb0afa8a0fbc47e76a5f9357da2898e0c7a0ee27 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 23 Sep 2024 02:06:11 +0200 Subject: [PATCH 122/127] stm32: update metapac. Fixes SPI version on L0. --- embassy-stm32/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 575c4f20c..8fc8da006 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,7 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-acaf04256034066bd5b3a8426224ccf3e4cb7d19" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9b7414490b10ffbd5beb1b0dcf14adb018cbe37f" } vcell = "0.1.3" nb = "1.0.0" @@ -99,7 +99,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-acaf04256034066bd5b3a8426224ccf3e4cb7d19", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9b7414490b10ffbd5beb1b0dcf14adb018cbe37f", default-features = false, features = ["metadata"] } [features] default = ["rt"] From a71098d824346a25ab9fb376627c0383d0f9250a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 22 Sep 2024 10:53:04 +0200 Subject: [PATCH 123/127] stm32/tests: test spi u8 and u16 word sizes. --- tests/stm32/src/bin/spi.rs | 100 ++++++++++++++++++---------- tests/stm32/src/bin/spi_dma.rs | 116 +++++++++++++++++++++------------ 2 files changed, 141 insertions(+), 75 deletions(-) diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index 53d44a94a..9712a8c5a 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs @@ -7,7 +7,8 @@ use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::spi::{self, Spi}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::spi::{self, Spi, Word}; use embassy_stm32::time::Hertz; #[embassy_executor::main] @@ -31,11 +32,58 @@ async fn main(_spawner: Spawner) { spi_config, ); - let data: [u8; 9] = [0x00, 0xFF, 0xAA, 0x55, 0xC0, 0xFF, 0xEE, 0xC0, 0xDE]; + test_txrx::(&mut spi); + test_txrx::(&mut spi); + + // Assert the RCC bit gets disabled on drop. + #[cfg(feature = "stm32f429zi")] + defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + drop(spi); + #[cfg(feature = "stm32f429zi")] + defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + + // test rx-only configuration + let mut spi = Spi::new_blocking_rxonly(&mut spi_peri, &mut sck, &mut miso, spi_config); + let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out); + test_rx::(&mut spi, &mut mosi_out); + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_blocking_txonly(&mut spi_peri, &mut sck, &mut mosi, spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = [0; 9]; + let mut buf = [W::default(); 9]; spi.blocking_transfer(&mut buf, &data).unwrap(); assert_eq!(buf, data); @@ -59,47 +107,33 @@ async fn main(_spawner: Spawner) { spi.blocking_transfer_in_place::(&mut []).unwrap(); spi.blocking_read::(&mut []).unwrap(); spi.blocking_write::(&[]).unwrap(); +} - // Assert the RCC bit gets disabled on drop. - #[cfg(feature = "stm32f429zi")] - defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); - drop(spi); - #[cfg(feature = "stm32f429zi")] - defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); +fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; - // test rx-only configuration - let mut spi = Spi::new_blocking_rxonly(&mut spi_peri, &mut sck, &mut miso, spi_config); - let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); mosi_out.set_high(); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); mosi_out.set_low(); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0x00; 9]); + assert_eq!(buf, [W::default(); 9]); spi.blocking_read::(&mut []).unwrap(); spi.blocking_read::(&mut []).unwrap(); - drop(mosi_out); - drop(spi); +} + +fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. - let mut spi = Spi::new_blocking_txonly(&mut spi_peri, &mut sck, &mut mosi, spi_config); - spi.blocking_transfer(&mut buf, &data).unwrap(); - spi.blocking_transfer_in_place(&mut buf).unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.blocking_read(&mut buf).unwrap(); - spi.blocking_transfer::(&mut [], &[]).unwrap(); - spi.blocking_transfer_in_place::(&mut []).unwrap(); - spi.blocking_read::(&mut []).unwrap(); - spi.blocking_write::(&[]).unwrap(); - drop(spi); - - // Test tx-only nosck. - let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); spi.blocking_write(&buf).unwrap(); spi.blocking_write::(&[]).unwrap(); spi.blocking_write(&buf).unwrap(); - drop(spi); - - info!("Test OK"); - cortex_m::asm::bkpt(); + spi.blocking_write::(&[]).unwrap(); } diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index a1cbc0ed1..307409a16 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs @@ -7,7 +7,8 @@ use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::spi::{self, Spi}; +use embassy_stm32::mode::Async; +use embassy_stm32::spi::{self, Spi, Word}; use embassy_stm32::time::Hertz; #[embassy_executor::main] @@ -35,11 +36,61 @@ async fn main(_spawner: Spawner) { spi_config, ); - let data: [u8; 9] = [0x00, 0xFF, 0xAA, 0x55, 0xC0, 0xFF, 0xEE, 0xC0, 0xDE]; + test_txrx::(&mut spi).await; + test_txrx::(&mut spi).await; + drop(spi); + + // test rx-only configuration + let mut spi = Spi::new_rxonly( + &mut spi_peri, + &mut sck, + &mut miso, + // SPIv1/f1 requires txdma even if rxonly. + #[cfg(not(feature = "spi-v345"))] + &mut tx_dma, + &mut rx_dma, + spi_config, + ); + let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out).await; + test_rx::(&mut spi, &mut mosi_out).await; + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_txonly(&mut spi_peri, &mut sck, &mut mosi, &mut tx_dma, spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +async fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = [0; 9]; + let mut buf = [W::default(); 9]; spi.transfer(&mut buf, &data).await.unwrap(); assert_eq!(buf, data); @@ -83,44 +134,41 @@ async fn main(_spawner: Spawner) { spi.blocking_write(&buf).unwrap(); spi.blocking_read(&mut buf).unwrap(); spi.write(&buf).await.unwrap(); +} - core::mem::drop(spi); +async fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; - // test rx-only configuration - let mut spi = Spi::new_rxonly( - &mut spi_peri, - &mut sck, - &mut miso, - // SPIv1/f1 requires txdma even if rxonly. - #[cfg(not(feature = "spi-v345"))] - &mut tx_dma, - &mut rx_dma, - spi_config, - ); - let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); mosi_out.set_high(); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); mosi_out.set_low(); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0x00; 9]); + assert_eq!(buf, [W::default(); 9]); spi.read::(&mut []).await.unwrap(); spi.blocking_read::(&mut []).unwrap(); - drop(mosi_out); - drop(spi); +} + +async fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. - let mut spi = Spi::new_txonly(&mut spi_peri, &mut sck, &mut mosi, &mut tx_dma, spi_config); spi.blocking_write(&buf).unwrap(); spi.write(&buf).await.unwrap(); spi.blocking_write(&buf).unwrap(); @@ -129,20 +177,4 @@ async fn main(_spawner: Spawner) { spi.write(&buf).await.unwrap(); spi.write::(&[]).await.unwrap(); spi.blocking_write::(&[]).unwrap(); - drop(spi); - - // Test tx-only nosck. - let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); - spi.blocking_write(&buf).unwrap(); - spi.write(&buf).await.unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.write(&buf).await.unwrap(); - spi.write(&buf).await.unwrap(); - spi.write::(&[]).await.unwrap(); - spi.blocking_write::(&[]).unwrap(); - drop(spi); - - info!("Test OK"); - cortex_m::asm::bkpt(); } From 05d453bfd81bce49d541b2eeee3a8788e5a5fd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20H=C3=A9riveaux?= Date: Mon, 23 Sep 2024 16:11:45 +0200 Subject: [PATCH 124/127] Fixed signature script in bootloader documentation --- docs/pages/bootloader.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/pages/bootloader.adoc b/docs/pages/bootloader.adoc index 3b0cdb182..a80e75c4c 100644 --- a/docs/pages/bootloader.adoc +++ b/docs/pages/bootloader.adoc @@ -86,8 +86,7 @@ Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware [source, bash] ---- -shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt -cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt +shasum -a 512 -b $FIRMWARE_DIR/myfirmware | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed From 0b8c4587c2726e4dd9186debb515034d687a22d3 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Mon, 23 Sep 2024 13:38:43 -0400 Subject: [PATCH 125/127] Fix rp2350b pins >31 on debug builds --- embassy-rp/src/gpio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 520043b07..cb54375e4 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -603,7 +603,7 @@ impl<'d> Flex<'d> { #[inline] fn bit(&self) -> u32 { - 1 << self.pin.pin() + 1 << (self.pin.pin() % 32) } /// Set the pin's pull. From a498bf11af2768b7aca24c3d84d4dfa20711c593 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Tue, 24 Sep 2024 18:45:20 -0400 Subject: [PATCH 126/127] Disable pad isolation on PWM A pins. Also fixes minor bug for 2040 where A pins didn't have their pull up/down enabled. --- embassy-rp/src/pwm.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 7da3dccb0..9ba3a2be3 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -106,6 +106,12 @@ impl<'d> Pwm<'d> { if let Some(pin) = &a { pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_pue(b_pull == Pull::Up); + w.set_pde(b_pull == Pull::Down); + }); } if let Some(pin) = &b { pin.gpio().ctrl().write(|w| w.set_funcsel(4)); From b743dce8e4777a3d1d3c2d1a40501be3af8b07e5 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Tue, 24 Sep 2024 18:55:05 -0400 Subject: [PATCH 127/127] Only B pins can be inputs. --- embassy-rp/src/pwm.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 9ba3a2be3..027f5504e 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -106,11 +106,9 @@ impl<'d> Pwm<'d> { if let Some(pin) = &a { pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + #[cfg(feature = "_rp235x")] pin.pad_ctrl().modify(|w| { - #[cfg(feature = "_rp235x")] w.set_iso(false); - w.set_pue(b_pull == Pull::Up); - w.set_pde(b_pull == Pull::Down); }); } if let Some(pin) = &b {