From 7ecb05ff77ba8cd87d87c1c6bc3b8c1a1624ad15 Mon Sep 17 00:00:00 2001 From: Matt Ickstadt Date: Fri, 13 Jan 2023 12:08:25 -0600 Subject: [PATCH 01/95] usb: allow setting the interface string for interface alt settings --- embassy-usb/src/builder.rs | 19 +++++++++++++++---- embassy-usb/src/class/cdc_acm.rs | 4 ++-- embassy-usb/src/class/cdc_ncm/mod.rs | 6 +++--- embassy-usb/src/class/hid.rs | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 87a8333b..785dc6f8 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -306,14 +306,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { /// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0. /// /// The first alternate setting, with number 0, is the default one. - pub fn alt_setting(&mut self, class: u8, subclass: u8, protocol: u8) -> InterfaceAltBuilder<'_, 'd, D> { + pub fn alt_setting( + &mut self, + class: u8, + subclass: u8, + protocol: u8, + interface_string: Option, + ) -> InterfaceAltBuilder<'_, 'd, D> { let number = self.next_alt_setting_number; self.next_alt_setting_number += 1; self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1; - self.builder - .config_descriptor - .interface_alt(self.interface_number, number, class, subclass, protocol, None); + self.builder.config_descriptor.interface_alt( + self.interface_number, + number, + class, + subclass, + protocol, + interface_string, + ); InterfaceAltBuilder { builder: self.builder, diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index 84db2062..09d7f774 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -171,7 +171,7 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { iface.handler(control); let comm_if = iface.interface_number(); let data_if = u8::from(comm_if) + 1; - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None); alt.descriptor( CS_INTERFACE, @@ -205,7 +205,7 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { // Data interface let mut iface = func.interface(); let data_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs index 4954a65b..d946eb11 100644 --- a/embassy-usb/src/class/cdc_ncm/mod.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -254,7 +254,7 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { mac_addr_str: [0; 12], })); let comm_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None); alt.descriptor( CS_INTERFACE, @@ -304,8 +304,8 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { let mut iface = func.interface(); iface.handler(state.data_control.write(DataControl {})); let data_if = iface.interface_number(); - let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); + let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs index b967aba0..12e49b7d 100644 --- a/embassy-usb/src/class/hid.rs +++ b/embassy-usb/src/class/hid.rs @@ -101,7 +101,7 @@ fn build<'d, D: Driver<'d>>( let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut iface = func.interface(); iface.handler(control); - let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); // HID descriptor alt.descriptor( From 0412d1922c6cca1200da0af54db3a5dbf60c035c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 21 Jan 2023 00:24:07 +0100 Subject: [PATCH 02/95] fix embedded-sdmmc integration. - Rename feature to `embedded-sdmmc`. - Move embedded-sdmmc fork repo to the embassy-rs org. - Remove unused features in the fork - Fix impl in embassy-stm32 - Add to CI so it doesn't break again. --- ci.sh | 2 +- embassy-stm32/Cargo.toml | 3 +-- embassy-stm32/src/sdmmc/mod.rs | 22 ++++++---------------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/ci.sh b/ci.sh index 323e752d..b325e376 100755 --- a/ci.sh +++ b/ci.sh @@ -63,7 +63,7 @@ cargo batch \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \ diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index d9c1f6dc..10e5e48c 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -58,7 +58,7 @@ cortex-m = "0.7.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand_core = "0.6.3" sdio-host = "0.5.0" -embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } +embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "46d1b1c2ff13e31e282ec1e352421721694f126a", optional = true } critical-section = "1.1" atomic-polyfill = "1.0.1" stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } @@ -77,7 +77,6 @@ stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", default-features [features] defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt"] -sdmmc-rs = ["embedded-sdmmc"] memory-x = ["stm32-metapac/memory-x"] subghz = [] exti = [] diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index a52c65b9..3f07e0c0 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -1532,7 +1532,7 @@ foreach_peripheral!( }; ); -#[cfg(feature = "sdmmc-rs")] +#[cfg(feature = "embedded-sdmmc")] mod sdmmc_rs { use core::future::Future; @@ -1540,7 +1540,7 @@ mod sdmmc_rs { use super::*; - impl<'d, T: Instance, P: Pins> BlockDevice for Sdmmc<'d, T, P> { + impl<'d, T: Instance, Dma: SdmmcDma> BlockDevice for Sdmmc<'d, T, Dma> { type Error = Error; type ReadFuture<'a> = impl Future> + 'a @@ -1558,19 +1558,14 @@ mod sdmmc_rs { _reason: &str, ) -> Self::ReadFuture<'a> { async move { - let card_capacity = self.card()?.card_type; - let inner = T::inner(); - let state = T::state(); let mut address = start_block_idx.0; for block in blocks.iter_mut() { let block: &mut [u8; 512] = &mut block.contents; // NOTE(unsafe) Block uses align(4) - let buf = unsafe { &mut *(block as *mut [u8; 512] as *mut [u32; 128]) }; - inner - .read_block(address, buf, card_capacity, state, self.config.data_transfer_timeout) - .await?; + let block = unsafe { &mut *(block as *mut _ as *mut DataBlock) }; + self.read_block(address, block).await?; address += 1; } Ok(()) @@ -1579,19 +1574,14 @@ mod sdmmc_rs { fn write<'a>(&'a mut self, blocks: &'a [Block], start_block_idx: BlockIdx) -> Self::WriteFuture<'a> { async move { - let card = self.card.as_mut().ok_or(Error::NoCard)?; - let inner = T::inner(); - let state = T::state(); let mut address = start_block_idx.0; for block in blocks.iter() { let block: &[u8; 512] = &block.contents; // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &*(block as *const [u8; 512] as *const [u32; 128]) }; - inner - .write_block(address, buf, card, state, self.config.data_transfer_timeout) - .await?; + let block = unsafe { &*(block as *const _ as *const DataBlock) }; + self.write_block(address, block).await?; address += 1; } Ok(()) From 5e3c33b77766df94f6021457c723ad52d97684b7 Mon Sep 17 00:00:00 2001 From: Christian Enderle Date: Sat, 21 Jan 2023 14:39:25 +0100 Subject: [PATCH 03/95] Fix rcc prescaler for wb55 HCLK1 - fix prescaler not divided which incorrectly set prescaler divided by 3 --- embassy-stm32/src/rcc/wb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/rcc/wb.rs b/embassy-stm32/src/rcc/wb.rs index f6a5feea..c9ada83e 100644 --- a/embassy-stm32/src/rcc/wb.rs +++ b/embassy-stm32/src/rcc/wb.rs @@ -64,7 +64,7 @@ impl Into for APBPrescaler { impl Into for AHBPrescaler { fn into(self) -> u8 { match self { - AHBPrescaler::NotDivided => 1, + AHBPrescaler::NotDivided => 0x0, AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div4 => 0x09, From 64e610fef708b7078e9690049583e05e16c6a3bd Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 23 Jan 2023 13:35:24 +0100 Subject: [PATCH 04/95] Replace `Level: Into` with `From` This automatically implements Into for Level --- embassy-nrf/src/gpio.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index b7230302..6ef5033e 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -88,9 +88,9 @@ impl From for Level { } } -impl Into for Level { - fn into(self) -> bool { - match self { +impl From for bool { + fn from(level: Level) -> bool { + match level { Level::Low => false, Level::High => true, } From f38d54a6a6a7fcfb836ea4606f719b2fed877250 Mon Sep 17 00:00:00 2001 From: Davide Della Giustina Date: Tue, 24 Jan 2023 08:15:22 +0000 Subject: [PATCH 05/95] IPv6 has no checksum --- embassy-net/src/device.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs index d0c8a62d..650af23b 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/device.rs @@ -57,10 +57,6 @@ where ), }; smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4); - #[cfg(feature = "proto-ipv6")] - { - smolcaps.checksum.ipv6 = convert(caps.checksum.ipv6); - } smolcaps.checksum.tcp = convert(caps.checksum.tcp); smolcaps.checksum.udp = convert(caps.checksum.udp); smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); From 32bdc54ccba9291b6aafc3c2695a8368d8137bb4 Mon Sep 17 00:00:00 2001 From: Davide Della Giustina Date: Tue, 24 Jan 2023 08:27:53 +0000 Subject: [PATCH 06/95] Changed crates' names for nrf examples since they were conflicting --- examples/nrf52840/Cargo.toml | 2 +- examples/nrf5340/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 994823a9..bbd8a5d2 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2021" -name = "embassy-nrf-examples" +name = "embassy-nrf52840-examples" version = "0.1.0" license = "MIT OR Apache-2.0" diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 03485711..eed49301 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2021" -name = "embassy-nrf-examples" +name = "embassy-nrf5340-examples" version = "0.1.0" license = "MIT OR Apache-2.0" From 2a0ea52878ece15fa12c9d70da32b5809aa1e713 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 24 Jan 2023 09:57:26 +0100 Subject: [PATCH 07/95] add missing copy of icmpv6 checksum add proto-ipv6 feature to stm32h7 example to catch issues in CI --- embassy-net/src/device.rs | 4 ++++ examples/stm32h7/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs index 650af23b..5daa0054 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/device.rs @@ -60,6 +60,10 @@ where smolcaps.checksum.tcp = convert(caps.checksum.tcp); smolcaps.checksum.udp = convert(caps.checksum.udp); smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); + #[cfg(feature = "proto-ipv6")] + { + smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6); + } smolcaps } diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index ff38440a..bcf97641 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } -embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits"] } +embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "proto-ipv6"] } embedded-io = { version = "0.4.0", features = ["async"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } From 34b67fe1372c535a659590744242cd4ffd52dfb6 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 26 Jan 2023 20:14:53 +0000 Subject: [PATCH 08/95] STD driver needs a reentrant mutex; logic fixed to be reentrancy-safe --- embassy-time/src/driver_std.rs | 67 +++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs index fc7fd197..da46a599 100644 --- a/embassy-time/src/driver_std.rs +++ b/embassy-time/src/driver_std.rs @@ -1,10 +1,12 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::mem::MaybeUninit; use std::sync::{Condvar, Mutex, Once}; use std::time::{Duration as StdDuration, Instant as StdInstant}; use std::{mem, ptr, thread}; use atomic_polyfill::{AtomicU8, Ordering}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex as EmbassyMutex; use crate::driver::{AlarmHandle, Driver}; @@ -35,7 +37,10 @@ struct TimeDriver { alarm_count: AtomicU8, once: Once, - alarms: UninitCell>, + // The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't + // Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections + // themselves are reentrant + alarms: UninitCell>>, zero_instant: UninitCell, signaler: UninitCell, } @@ -53,7 +58,8 @@ crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { impl TimeDriver { fn init(&self) { self.once.call_once(|| unsafe { - self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); + self.alarms + .write(EmbassyMutex::new(RefCell::new([ALARM_NEW; ALARM_COUNT]))); self.zero_instant.write(StdInstant::now()); self.signaler.write(Signaler::new()); @@ -66,25 +72,37 @@ impl TimeDriver { loop { let now = DRIVER.now(); - let mut next_alarm = u64::MAX; - { - let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); - for alarm in alarms { - if alarm.timestamp <= now { - alarm.timestamp = u64::MAX; + let next_alarm = unsafe { DRIVER.alarms.as_ref() }.lock(|alarms| { + loop { + let pending = alarms + .borrow_mut() + .iter_mut() + .find(|alarm| alarm.timestamp <= now) + .map(|alarm| { + alarm.timestamp = u64::MAX; - // Call after clearing alarm, so the callback can set another alarm. + (alarm.callback, alarm.ctx) + }); + if let Some((callback, ctx)) = pending { // safety: // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; - f(alarm.ctx); + let f: fn(*mut ()) = unsafe { mem::transmute(callback) }; + f(ctx); } else { - next_alarm = next_alarm.min(alarm.timestamp); + // No alarm due + break; } } - } + + alarms + .borrow() + .iter() + .map(|alarm| alarm.timestamp) + .min() + .unwrap_or(u64::MAX) + }); // Ensure we don't overflow let until = zero @@ -121,18 +139,23 @@ impl Driver for TimeDriver { fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.callback = callback as *const (); - alarm.ctx = ctx; + unsafe { self.alarms.as_ref() }.lock(|alarms| { + let mut alarms = alarms.borrow_mut(); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.callback = callback as *const (); + alarm.ctx = ctx; + }); } fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.timestamp = timestamp; - unsafe { self.signaler.as_ref() }.signal(); + unsafe { self.alarms.as_ref() }.lock(|alarms| { + let mut alarms = alarms.borrow_mut(); + + let alarm = &mut alarms[alarm.id() as usize]; + alarm.timestamp = timestamp; + unsafe { self.signaler.as_ref() }.signal(); + }); true } From 1e60c60afdfc2a4677a3312d7af8d065af6c50ac Mon Sep 17 00:00:00 2001 From: nitroxis Date: Fri, 27 Jan 2023 07:19:34 +0100 Subject: [PATCH 09/95] rp: allow isochronous USB endpoints to be up to 1023 in size --- embassy-rp/src/usb.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 2710c4ad..8eec55b4 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -219,14 +219,16 @@ impl<'d, T: Instance> Driver<'d, T> { let (index, ep) = index.ok_or(EndpointAllocError)?; assert!(!ep.used); - if max_packet_size > 64 { + // as per datasheet, the maximum buffer size is 64, except for isochronous + // endpoints, which are allowed to be up to 1023 bytes. + if (ep_type != EndpointType::Isochronous && max_packet_size > 64) || max_packet_size > 1023 { warn!("max_packet_size too high: {}", max_packet_size); return Err(EndpointAllocError); } // ep mem addrs must be 64-byte aligned, so there's no point in trying // to allocate smaller chunks to save memory. - let len = 64; + let len = (max_packet_size + 63) / 64 * 64; let addr = self.ep_mem_free; if addr + len > EP_MEMORY_SIZE as _ { From c9e2cd6dd41f9281f89ba40cc57dea07b9a224b8 Mon Sep 17 00:00:00 2001 From: nitroxis Date: Fri, 27 Jan 2023 15:49:19 +0100 Subject: [PATCH 10/95] usb: allow adding isochronous endpoints --- embassy-usb/src/builder.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 87a8333b..fc16d2b4 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -401,4 +401,17 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval) } + + /// 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: u8) -> D::EndpointIn { + self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval) + } + + /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. + pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { + self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval) + } } From e453334870575dd0d18f65dcd207bfbff6e88990 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 27 Jan 2023 15:36:26 +0100 Subject: [PATCH 11/95] LoRa/SX1276: adjust Rx window offset and duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a transmission, two receive windows Rx1 and Rx2 are opened for one second each, one right after the other, after a fixed delay (for example 5s). The Rx window offset is added to the starting date of each window and the Rx window duration represents the maximum delay we will wait for an incoming message before declaring that a timeout occurred. A value of -500ms for the offset and 800ms for the duration means that instead of having Rx1 = [5000, 6000[ and Rx2 = [6000, 7000[ we get Rx1 = [4500, 5300[ and Rx2 = [5500, 6300[. We only cover 30% of the expected windows. The maximum time a SX127x can take before the Rx side is ready is TS_HOP + TS_RE = 50µs + 2.33ms. Using 3ms for the offset and 1003ms for the duration will give much better time windows: Rx1 = [4997, 5997[ and Rx2 = [5997, 7000]. Note that the lorawan-device crate caps Rx1 end date to Rx2 start date. This change allows a previously failing Murata CMWX1ZZABZ-091 module (STM32L + SX1276) to connect to the TTN LoRa network. --- embassy-lora/src/sx127x/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-lora/src/sx127x/mod.rs b/embassy-lora/src/sx127x/mod.rs index f47a9eb5..8904c9a1 100644 --- a/embassy-lora/src/sx127x/mod.rs +++ b/embassy-lora/src/sx127x/mod.rs @@ -70,10 +70,10 @@ where RFS: RadioSwitch + 'static, { fn get_rx_window_offset_ms(&self) -> i32 { - -500 + -3 } fn get_rx_window_duration_ms(&self) -> u32 { - 800 + 1003 } } From 48e1aab762e902ee0a132602d3c2f9ec0551cd6b Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Sun, 29 Jan 2023 12:55:06 -0600 Subject: [PATCH 12/95] executor: Replace `NonNull` with `TaskRef` --- embassy-executor/src/raw/mod.rs | 63 ++++++++++++++++++------- embassy-executor/src/raw/run_queue.rs | 13 ++--- embassy-executor/src/raw/timer_queue.rs | 35 ++++++-------- embassy-executor/src/raw/waker.rs | 13 +++-- embassy-executor/src/spawner.rs | 9 ++-- 5 files changed, 76 insertions(+), 57 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 181dabe8..10a154a9 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -43,14 +43,11 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; /// Raw task header for use in task pointers. -/// -/// This is an opaque struct, used for raw pointers to tasks, for use -/// with funtions like [`wake_task`] and [`task_from_waker`]. -pub struct TaskHeader { +pub(crate) struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, - pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 - pub(crate) poll_fn: UninitCell)>, // Valid if STATE_SPAWNED + pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 + pub(crate) poll_fn: UninitCell, // Valid if STATE_SPAWNED #[cfg(feature = "integrated-timers")] pub(crate) expires_at: Cell, @@ -59,7 +56,7 @@ pub struct TaskHeader { } impl TaskHeader { - pub(crate) const fn new() -> Self { + const fn new() -> Self { Self { state: AtomicU32::new(0), run_queue_item: RunQueueItem::new(), @@ -74,6 +71,36 @@ impl TaskHeader { } } +/// This is essentially a `&'static TaskStorage` where the type of the future has been erased. +#[derive(Clone, Copy)] +pub struct TaskRef { + ptr: NonNull, +} + +impl TaskRef { + fn new(task: &'static TaskStorage) -> Self { + Self { + ptr: NonNull::from(task).cast(), + } + } + + /// Safety: The pointer must have been obtained with `Task::as_ptr` + pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self { + Self { + ptr: NonNull::new_unchecked(ptr as *mut TaskHeader), + } + } + + pub(crate) fn header(self) -> &'static TaskHeader { + unsafe { self.ptr.as_ref() } + } + + /// The returned pointer is valid for the entire TaskStorage. + pub(crate) fn as_ptr(self) -> *const TaskHeader { + self.ptr.as_ptr() + } +} + /// Raw storage in which a task can be spawned. /// /// This struct holds the necessary memory to spawn one task whose future is `F`. @@ -135,14 +162,14 @@ impl TaskStorage { .is_ok() } - unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull { + unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> TaskRef { // Initialize the task self.raw.poll_fn.write(Self::poll); self.future.write(future()); - NonNull::new_unchecked(self as *const TaskStorage as *const TaskHeader as *mut TaskHeader) + TaskRef::new(self) } - unsafe fn poll(p: NonNull) { + unsafe fn poll(p: TaskRef) { let this = &*(p.as_ptr() as *const TaskStorage); let future = Pin::new_unchecked(this.future.as_mut()); @@ -307,7 +334,7 @@ impl Executor { /// - `task` must be set up to run in this executor. /// - `task` must NOT be already enqueued (in this executor or another one). #[inline(always)] - unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull) { + unsafe fn enqueue(&self, cs: CriticalSection, task: TaskRef) { #[cfg(feature = "rtos-trace")] trace::task_ready_begin(task.as_ptr() as u32); @@ -325,8 +352,8 @@ impl Executor { /// It is OK to use `unsafe` to call this from a thread that's not the executor thread. /// In this case, the task's Future must be Send. This is because this is effectively /// sending the task to the executor thread. - pub(super) unsafe fn spawn(&'static self, task: NonNull) { - task.as_ref().executor.set(self); + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + task.header().executor.set(self); #[cfg(feature = "rtos-trace")] trace::task_new(task.as_ptr() as u32); @@ -359,7 +386,7 @@ impl Executor { self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); self.run_queue.dequeue_all(|p| { - let task = p.as_ref(); + let task = p.header(); #[cfg(feature = "integrated-timers")] task.expires_at.set(Instant::MAX); @@ -378,7 +405,7 @@ impl Executor { trace::task_exec_begin(p.as_ptr() as u32); // Run the task - task.poll_fn.read()(p as _); + task.poll_fn.read()(p); #[cfg(feature = "rtos-trace")] trace::task_exec_end(); @@ -424,9 +451,9 @@ impl Executor { /// # Safety /// /// `task` must be a valid task pointer obtained from [`task_from_waker`]. -pub unsafe fn wake_task(task: NonNull) { +pub unsafe fn wake_task(task: TaskRef) { critical_section::with(|cs| { - let header = task.as_ref(); + let header = task.header(); let state = header.state.load(Ordering::Relaxed); // If already scheduled, or if not started, @@ -450,7 +477,7 @@ struct TimerQueue; impl embassy_time::queue::TimerQueue for TimerQueue { fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) { let task = waker::task_from_waker(waker); - let task = unsafe { task.as_ref() }; + let task = task.header(); let expires_at = task.expires_at.get(); task.expires_at.set(expires_at.min(at)); } diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs index ed8c82a5..36215753 100644 --- a/embassy-executor/src/raw/run_queue.rs +++ b/embassy-executor/src/raw/run_queue.rs @@ -4,7 +4,7 @@ use core::ptr::NonNull; use atomic_polyfill::{AtomicPtr, Ordering}; use critical_section::CriticalSection; -use super::TaskHeader; +use super::{TaskHeader, TaskRef}; pub(crate) struct RunQueueItem { next: AtomicPtr, @@ -46,25 +46,26 @@ impl RunQueue { /// /// `item` must NOT be already enqueued in any queue. #[inline(always)] - pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull) -> bool { + pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: TaskRef) -> bool { let prev = self.head.load(Ordering::Relaxed); - task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed); - self.head.store(task.as_ptr(), Ordering::Relaxed); + task.header().run_queue_item.next.store(prev, Ordering::Relaxed); + self.head.store(task.as_ptr() as _, Ordering::Relaxed); prev.is_null() } /// Empty the queue, then call `on_task` for each task that was in the queue. /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. - pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull)) { + pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { // Atomically empty the queue. let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); // Iterate the linked list of tasks that were previously in the queue. while let Some(task) = NonNull::new(ptr) { + let task = unsafe { TaskRef::from_ptr(task.as_ptr()) }; // If the task re-enqueues itself, the `next` pointer will get overwritten. // Therefore, first read the next pointer, and only then process the task. - let next = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed); + let next = task.header().run_queue_item.next.load(Ordering::Relaxed); on_task(task); diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs index 24c31892..57d6d3cd 100644 --- a/embassy-executor/src/raw/timer_queue.rs +++ b/embassy-executor/src/raw/timer_queue.rs @@ -1,45 +1,39 @@ use core::cell::Cell; use core::cmp::min; -use core::ptr; -use core::ptr::NonNull; use atomic_polyfill::Ordering; use embassy_time::Instant; -use super::{TaskHeader, STATE_TIMER_QUEUED}; +use super::{TaskRef, STATE_TIMER_QUEUED}; pub(crate) struct TimerQueueItem { - next: Cell<*mut TaskHeader>, + next: Cell>, } impl TimerQueueItem { pub const fn new() -> Self { - Self { - next: Cell::new(ptr::null_mut()), - } + Self { next: Cell::new(None) } } } pub(crate) struct TimerQueue { - head: Cell<*mut TaskHeader>, + head: Cell>, } impl TimerQueue { pub const fn new() -> Self { - Self { - head: Cell::new(ptr::null_mut()), - } + Self { head: Cell::new(None) } } - pub(crate) unsafe fn update(&self, p: NonNull) { - let task = p.as_ref(); + pub(crate) unsafe fn update(&self, p: TaskRef) { + let task = p.header(); if task.expires_at.get() != Instant::MAX { let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); let is_new = old_state & STATE_TIMER_QUEUED == 0; if is_new { task.timer_queue_item.next.set(self.head.get()); - self.head.set(p.as_ptr()); + self.head.set(Some(p)); } } } @@ -47,7 +41,7 @@ impl TimerQueue { pub(crate) unsafe fn next_expiration(&self) -> Instant { let mut res = Instant::MAX; self.retain(|p| { - let task = p.as_ref(); + let task = p.header(); let expires = task.expires_at.get(); res = min(res, expires); expires != Instant::MAX @@ -55,9 +49,9 @@ impl TimerQueue { res } - pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull)) { + pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(TaskRef)) { self.retain(|p| { - let task = p.as_ref(); + let task = p.header(); if task.expires_at.get() <= now { on_task(p); false @@ -67,11 +61,10 @@ impl TimerQueue { }); } - pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull) -> bool) { + pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { let mut prev = &self.head; - while !prev.get().is_null() { - let p = NonNull::new_unchecked(prev.get()); - let task = &*p.as_ptr(); + while let Some(p) = prev.get() { + let task = p.header(); if f(p) { // Skip to next prev = &task.timer_queue_item.next; diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 5765259f..400b37fa 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs @@ -1,8 +1,7 @@ use core::mem; -use core::ptr::NonNull; use core::task::{RawWaker, RawWakerVTable, Waker}; -use super::{wake_task, TaskHeader}; +use super::{wake_task, TaskHeader, TaskRef}; const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); @@ -11,14 +10,14 @@ unsafe fn clone(p: *const ()) -> RawWaker { } unsafe fn wake(p: *const ()) { - wake_task(NonNull::new_unchecked(p as *mut TaskHeader)) + wake_task(TaskRef::from_ptr(p as *const TaskHeader)) } unsafe fn drop(_: *const ()) { // nop } -pub(crate) unsafe fn from_task(p: NonNull) -> Waker { +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) } @@ -33,7 +32,7 @@ pub(crate) unsafe fn from_task(p: NonNull) -> Waker { /// # Panics /// /// Panics if the waker is not created by the Embassy executor. -pub fn task_from_waker(waker: &Waker) -> NonNull { +pub fn task_from_waker(waker: &Waker) -> TaskRef { // safety: OK because WakerHack has the same layout as Waker. // This is not really guaranteed because the structs are `repr(Rust)`, it is // indeed the case in the current implementation. @@ -43,8 +42,8 @@ pub fn task_from_waker(waker: &Waker) -> NonNull { panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") } - // safety: we never create a waker with a null data pointer. - unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) } + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) } } struct WakerHack { diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 400d973f..650ea06c 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -1,7 +1,6 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::mem; -use core::ptr::NonNull; use core::task::Poll; use super::raw; @@ -22,12 +21,12 @@ use super::raw; /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] pub struct SpawnToken { - raw_task: Option>, + raw_task: Option, phantom: PhantomData<*mut S>, } impl SpawnToken { - pub(crate) unsafe fn new(raw_task: NonNull) -> Self { + pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { Self { raw_task: Some(raw_task), phantom: PhantomData, @@ -92,7 +91,7 @@ impl Spawner { pub async fn for_current_executor() -> Self { poll_fn(|cx| unsafe { let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); + let executor = task.header().executor.get(); Poll::Ready(Self::new(&*executor)) }) .await @@ -168,7 +167,7 @@ impl SendSpawner { pub async fn for_current_executor() -> Self { poll_fn(|cx| unsafe { let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); + let executor = task.header().executor.get(); Poll::Ready(Self::new(&*executor)) }) .await From b6ca6d699ad529f56901485fed86bee1ececad6d Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Sun, 29 Jan 2023 16:32:12 -0600 Subject: [PATCH 13/95] Make `wake_task` safe --- embassy-executor/src/raw/mod.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 10a154a9..183c5e6a 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -444,14 +444,10 @@ impl Executor { } } -/// Wake a task by raw pointer. +/// Wake a task by `TaskRef`. /// -/// You can obtain task pointers from `Waker`s using [`task_from_waker`]. -/// -/// # Safety -/// -/// `task` must be a valid task pointer obtained from [`task_from_waker`]. -pub unsafe fn wake_task(task: TaskRef) { +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task(task: TaskRef) { critical_section::with(|cs| { let header = task.header(); let state = header.state.load(Ordering::Relaxed); @@ -465,8 +461,10 @@ pub unsafe fn wake_task(task: TaskRef) { header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed); // We have just marked the task as scheduled, so enqueue it. - let executor = &*header.executor.get(); - executor.enqueue(cs, task); + unsafe { + let executor = &*header.executor.get(); + executor.enqueue(cs, task); + } }) } From 768fe699cfbe10016cd4bb93bb78f9d89e1a8e96 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 31 Jan 2023 19:36:37 +0100 Subject: [PATCH 14/95] Pass the correct buffer when creating TcpSocket --- embassy-net/src/tcp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 0fbf0c91..d46bd4db 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -389,7 +389,7 @@ pub mod client { fn new(stack: &'d Stack, 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().0, &mut bufs.as_mut().1) }, + socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, state, bufs, }) From ca10fe7135d10084e38038f3cd433da39e505bea Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 31 Jan 2023 22:27:19 +0100 Subject: [PATCH 15/95] usb: docs --- embassy-nrf/src/usb.rs | 8 +- embassy-rp/src/usb.rs | 16 +-- embassy-stm32/src/usb/usb.rs | 16 +-- embassy-stm32/src/usb_otg/usb.rs | 16 +-- embassy-usb-driver/README.md | 32 +++++ embassy-usb-driver/src/lib.rs | 116 ++++++++++++------- embassy-usb/README.md | 22 ++++ embassy-usb/src/builder.rs | 24 ++-- embassy-usb/src/class/cdc_acm.rs | 9 ++ embassy-usb/src/class/cdc_ncm/embassy_net.rs | 11 ++ embassy-usb/src/class/cdc_ncm/mod.rs | 51 +++++--- embassy-usb/src/class/hid.rs | 21 ++++ embassy-usb/src/class/mod.rs | 1 + embassy-usb/src/control.rs | 7 ++ embassy-usb/src/descriptor.rs | 4 +- embassy-usb/src/lib.rs | 8 +- embassy-usb/src/types.rs | 2 + 17 files changed, 267 insertions(+), 97 deletions(-) create mode 100644 embassy-usb-driver/README.md create mode 100644 embassy-usb/README.md diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 6be4fec8..f030b909 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -235,7 +235,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_in.allocate(ep_type)?; let ep_addr = EndpointAddress::from_parts(index, Direction::In); @@ -243,7 +243,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } @@ -251,7 +251,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_out.allocate(ep_type)?; let ep_addr = EndpointAddress::from_parts(index, Direction::Out); @@ -259,7 +259,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 8eec55b4..2e3708ef 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -194,13 +194,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -281,7 +281,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -298,18 +298,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index 03e792a2..0355c5f1 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -268,13 +268,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -345,7 +345,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -362,18 +362,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs index fd2f6707..96c574ca 100644 --- a/embassy-stm32/src/usb_otg/usb.rs +++ b/embassy-stm32/src/usb_otg/usb.rs @@ -217,13 +217,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -292,7 +292,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, }) } @@ -308,18 +308,18 @@ impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { diff --git a/embassy-usb-driver/README.md b/embassy-usb-driver/README.md new file mode 100644 index 00000000..012663c3 --- /dev/null +++ b/embassy-usb-driver/README.md @@ -0,0 +1,32 @@ +# embassy-usb-driver + +This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these +traits to add support for using `embassy-usb` for a given chip/platform. + +The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`] +APIs don't cause a semver-major bump of thsi crate. This allows existing HALs/BSPs to be used +with the newer `embassy-usb` without needing updates. + +If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate +instead of this one. + +[`embassy-usb`]: https://crates.io/crates/embassy-usb + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs index 64d64735..e8f71a2d 100644 --- a/embassy-usb-driver/src/lib.rs +++ b/embassy-usb-driver/src/lib.rs @@ -1,6 +1,8 @@ #![no_std] #![feature(async_fn_in_trait)] #![allow(incomplete_features)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] /// Direction of USB traffic. Note that in the USB standard the direction is always indicated from /// the perspective of the host, which is backward for devices, but the standard directions are used @@ -95,46 +97,65 @@ impl EndpointAddress { } } +/// Infomation for an endpoint. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointInfo { + /// Endpoint's address. pub addr: EndpointAddress, + /// Endpoint's type. pub ep_type: EndpointType, + /// Max packet size, in bytes. pub max_packet_size: u16, - pub interval: u8, + /// Polling interval, in milliseconds. + pub interval_ms: u8, } -/// Driver for a specific USB peripheral. Implement this to add support for a new hardware -/// platform. +/// Main USB driver trait. +/// +/// Implement this to add support for a new hardware platform. pub trait Driver<'a> { + /// Type of the OUT endpoints for this driver. type EndpointOut: EndpointOut + 'a; + /// Type of the IN endpoints for this driver. type EndpointIn: EndpointIn + 'a; + /// Type of the control pipe for this driver. type ControlPipe: ControlPipe + 'a; + /// Type for bus control for this driver. type Bus: Bus + 'a; - /// Allocates an endpoint and specified endpoint parameters. This method is called by the device - /// and class implementations to allocate endpoints, and can only be called before - /// [`start`](Self::start) is called. + /// Allocates an OUT endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. /// /// # Arguments /// - /// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should - /// attempt to return an endpoint with the specified address. If None, the implementation - /// should return the next available one. + /// * `ep_type` - the endpoint's type. /// * `max_packet_size` - Maximum packet size in bytes. - /// * `interval` - Polling interval parameter for interrupt endpoints. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result; + /// Allocates an IN endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. fn alloc_endpoint_in( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result; /// Start operation of the USB device. @@ -146,35 +167,37 @@ pub trait Driver<'a> { /// This consumes the `Driver` instance, so it's no longer possible to allocate more /// endpoints. fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); - - /// Indicates that `set_device_address` must be called before accepting the corresponding - /// control transfer, not after. - /// - /// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6 - const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; } +/// USB bus trait. +/// +/// This trait provides methods that act on the whole bus. It is kept owned by +/// the main USB task, and used to manage the bus. pub trait Bus { - /// Enables the USB peripheral. Soon after enabling the device will be reset, so - /// there is no need to perform a USB reset in this method. + /// Enable the USB peripheral. async fn enable(&mut self); - /// Disables and powers down the USB peripheral. + /// Disable and powers down the USB peripheral. async fn disable(&mut self); + /// Wait for a bus-related event. + /// + /// This method should asynchronously wait for an event to happen, then + /// return it. See [`Event`] for the list of events this method should return. async fn poll(&mut self) -> Event; - /// Enables or disables an endpoint. + /// Enable or disable an endpoint. fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); - /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it - /// should be prepared to receive data again. Only used during control transfers. + /// Set or clear the STALL condition for an endpoint. + /// + /// If the endpoint is an OUT endpoint, it should be prepared to receive data again. fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); - /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. + /// Get whether the STALL condition is set for an endpoint. fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; - /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the /// device. /// /// The default implementation just returns `Unsupported`. @@ -187,7 +210,7 @@ pub trait Bus { Err(Unsupported) } - /// Initiates a remote wakeup of the host by the device. + /// Initiate a remote wakeup of the host by the device. /// /// # Errors /// @@ -196,25 +219,27 @@ pub trait Bus { async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; } +/// Endpoint trait, common for OUT and IN. pub trait Endpoint { /// Get the endpoint address fn info(&self) -> &EndpointInfo; - /// Waits for the endpoint to be enabled. + /// Wait for the endpoint to be enabled. async fn wait_enabled(&mut self); } +/// OUT Endpoint trait. pub trait EndpointOut: Endpoint { - /// Reads a single packet of data from the endpoint, and returns the actual length of + /// Read a single packet of data from the endpoint, and return the actual length of /// the packet. /// /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. async fn read(&mut self, buf: &mut [u8]) -> Result; } -/// Trait for USB control pipe. +/// USB control pipe trait. /// -/// The USB control pipe owns both OUT ep 0 and IN ep 0 in a single +/// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single /// unit, and manages them together to implement the control pipe state machine. /// /// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that @@ -232,6 +257,14 @@ pub trait EndpointOut: Endpoint { /// accept() or reject() /// ``` /// +/// - control out for setting the device address: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept_set_address(addr) or reject() +/// ``` +/// /// - control out with len != 0: /// /// ```not_rust @@ -280,26 +313,26 @@ pub trait ControlPipe { /// Maximum packet size for the control pipe fn max_packet_size(&self) -> usize; - /// Reads a single setup packet from the endpoint. + /// Read a single setup packet from the endpoint. async fn setup(&mut self) -> [u8; 8]; - /// Reads a DATA OUT packet into `buf` in response to a control write request. + /// Read a DATA OUT packet into `buf` in response to a control write request. /// /// Must be called after `setup()` for requests with `direction` of `Out` /// and `length` greater than zero. async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result; - /// Sends a DATA IN packet with `data` in response to a control read request. + /// Send a DATA IN packet with `data` in response to a control read request. /// /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; - /// Accepts a control request. + /// Accept a control request. /// /// Causes the STATUS packet for the current request to be ACKed. async fn accept(&mut self); - /// Rejects a control request. + /// Reject a control request. /// /// Sets a STALL condition on the pipe to indicate an error. async fn reject(&mut self); @@ -311,8 +344,9 @@ pub trait ControlPipe { async fn accept_set_address(&mut self, addr: u8); } +/// IN Endpoint trait. pub trait EndpointIn: Endpoint { - /// Writes a single packet of data to the endpoint. + /// Write a single packet of data to the endpoint. async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; } @@ -338,18 +372,22 @@ pub enum Event { PowerRemoved, } +/// Allocating an endpoint failed. +/// +/// This can be due to running out of endpoints, or out of endpoint memory, +/// or because the hardware doesn't support the requested combination of features. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointAllocError; +/// Operation is unsupported by the driver. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Operation is unsupported by the driver. pub struct Unsupported; +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] pub enum EndpointError { /// Either the packet to be written is too long to fit in the transmission /// buffer or the received packet is too long to fit in `buf`. diff --git a/embassy-usb/README.md b/embassy-usb/README.md new file mode 100644 index 00000000..581c3290 --- /dev/null +++ b/embassy-usb/README.md @@ -0,0 +1,22 @@ +# embassy-usb + +TODO crate description/ + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index fc16d2b4..48498994 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -349,11 +349,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.config_descriptor.write(descriptor_type, descriptor) } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn { + fn 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) + .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -361,11 +361,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut { + fn 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) + .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -393,25 +393,25 @@ 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: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval) + 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) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. - pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval) + 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) } /// 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: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval) + 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) } /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. - pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval) + 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) } } diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index 84db2062..09f17456 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -1,3 +1,5 @@ +//! CDC-ACM class implementation, aka Serial over USB. + use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; @@ -28,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; +/// Internal state for CDC-ACM pub struct State<'a> { control: MaybeUninit>, shared: ControlShared, } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { control: MaybeUninit::uninit(), @@ -284,10 +288,15 @@ impl From for StopBits { #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ParityType { + /// No parity bit. None = 0, + /// Parity bit is 1 if the amount of `1` bits in the data byte is odd. Odd = 1, + /// Parity bit is 1 if the amount of `1` bits in the data byte is even. Even = 2, + /// Parity bit is always 1 Mark = 3, + /// Parity bit is always 0 Space = 4, } diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs index 501df2d8..bc79b367 100644 --- a/embassy-usb/src/class/cdc_ncm/embassy_net.rs +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -1,3 +1,4 @@ +//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class. use embassy_futures::select::{select, Either}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; @@ -5,11 +6,13 @@ use embassy_usb_driver::Driver; use super::{CdcNcmClass, Receiver, Sender}; +/// Internal state for the embassy-net integration. pub struct State { ch_state: ch::State, } impl State { + /// Create a new `State`. pub const fn new() -> Self { Self { ch_state: ch::State::new(), @@ -17,6 +20,9 @@ impl State, const MTU: usize> { tx_usb: Sender<'d, D>, rx_usb: Receiver<'d, D>, @@ -24,6 +30,9 @@ pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { } impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + /// Run the CDC-NCM class. + /// + /// You must call this in a background task for the class to operate. pub async fn run(mut self) -> ! { let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); let rx_fut = async move { @@ -66,9 +75,11 @@ impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { // would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug? //pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd; +/// Type alias for the embassy-net driver for CDC-NCM. pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>; impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Obtain a driver for using the CDC-NCM class with [`embassy-net`](crates.io/crates/embassy-net). pub fn into_embassy_net_device( self, state: &'d mut State, diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs index 4954a65b..5e59b72f 100644 --- a/embassy-usb/src/class/cdc_ncm/mod.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -1,18 +1,19 @@ -/// CDC-NCM, aka Ethernet over USB. -/// -/// # Compatibility -/// -/// Windows: NOT supported in Windows 10. Supported in Windows 11. -/// -/// Linux: Well-supported since forever. -/// -/// Android: Support for CDC-NCM is spotty and varies across manufacturers. -/// -/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12. -/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), -/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. -/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 -/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 +//! CDC-NCM class implementation, aka Ethernet over USB. +//! +//! # Compatibility +//! +//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11. +//! +//! Linux: Well-supported since forever. +//! +//! Android: Support for CDC-NCM is spotty and varies across manufacturers. +//! +//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +//! This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 +//! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 + use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; @@ -114,6 +115,7 @@ fn byteify(buf: &mut [u8], data: T) -> &[u8] { &buf[..len] } +/// Internal state for the CDC-NCM class. pub struct State<'a> { comm_control: MaybeUninit>, data_control: MaybeUninit, @@ -121,6 +123,7 @@ pub struct State<'a> { } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { comm_control: MaybeUninit::uninit(), @@ -223,6 +226,7 @@ impl ControlHandler for DataControl { } } +/// CDC-NCM class pub struct CdcNcmClass<'d, D: Driver<'d>> { _comm_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -235,6 +239,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Create a new CDC NCM class. pub fn new( builder: &mut Builder<'d, D>, state: &'d mut State<'d>, @@ -319,6 +324,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { ( Sender { @@ -334,12 +342,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } +/// CDC NCM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcNcmClass::split`] pub struct Sender<'d, D: Driver<'d>> { write_ep: D::EndpointIn, seq: u16, } impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Write a packet. + /// + /// This waits until the packet is succesfully stored in the CDC-NCM endpoint buffers. pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { let seq = self.seq; self.seq = self.seq.wrapping_add(1); @@ -393,6 +407,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { } } +/// CDC NCM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcNcmClass::split`] pub struct Receiver<'d, D: Driver<'d>> { data_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -400,7 +417,9 @@ pub struct Receiver<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> Receiver<'d, D> { - /// Reads a single packet from the OUT endpoint. + /// Write a network packet. + /// + /// This waits until a packet is succesfully received from the endpoint buffers. pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { // Retry loop loop { diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs index b967aba0..2493061b 100644 --- a/embassy-usb/src/class/hid.rs +++ b/embassy-usb/src/class/hid.rs @@ -1,3 +1,5 @@ +//! USB HID (Human Interface Device) class implementation. + use core::mem::MaybeUninit; use core::ops::Range; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -28,6 +30,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09; const HID_REQ_GET_PROTOCOL: u8 = 0x03; const HID_REQ_SET_PROTOCOL: u8 = 0x0b; +/// Configuration for the HID class. pub struct Config<'d> { /// HID report descriptor. pub report_descriptor: &'d [u8], @@ -46,11 +49,15 @@ pub struct Config<'d> { pub max_packet_size: u16, } +/// Report ID #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReportId { + /// IN report In(u8), + /// OUT report Out(u8), + /// Feature report Feature(u8), } @@ -65,12 +72,14 @@ impl ReportId { } } +/// Internal state for USB HID. pub struct State<'d> { control: MaybeUninit>, out_report_offset: AtomicUsize, } impl<'d> State<'d> { + /// Create a new `State`. pub fn new() -> Self { State { control: MaybeUninit::uninit(), @@ -79,6 +88,7 @@ impl<'d> State<'d> { } } +/// USB HID reader/writer. pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { reader: HidReader<'d, D, READ_N>, writer: HidWriter<'d, D, WRITE_N>, @@ -180,20 +190,30 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit } } +/// USB HID writer. +/// +/// You can obtain a `HidWriter` using [`HidReaderWriter::split`]. pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { ep_in: D::EndpointIn, } +/// USB HID reader. +/// +/// You can obtain a `HidReader` using [`HidReaderWriter::split`]. pub struct HidReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, offset: &'d AtomicUsize, } +/// Error when reading a HID report. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadError { + /// The given buffer was too small to read the received report. BufferOverflow, + /// The endpoint is disabled. Disabled, + /// The report was only partially read. See [`HidReader::read`] for details. Sync(Range), } @@ -344,6 +364,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { } } +/// Handler for HID-related control requests. pub trait RequestHandler { /// Reads the value of report `id` into `buf` returning the size. /// diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index af27577a..b23e03d4 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -1,3 +1,4 @@ +//! Implementations of well-known USB classes. pub mod cdc_acm; pub mod cdc_ncm; pub mod hid; diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index d6d0c656..39b499f0 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -126,17 +126,23 @@ impl Request { } } +/// Response for a CONTROL OUT request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OutResponse { + /// The request was accepted. Accepted, + /// The request was rejected. Rejected, } +/// Response for a CONTROL IN request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InResponse<'a> { + /// The request was accepted. The buffer contains the response data. Accepted(&'a [u8]), + /// The request was rejected. Rejected, } @@ -148,6 +154,7 @@ pub trait ControlHandler { /// Called after a USB reset after the bus reset sequence is complete. fn reset(&mut self) {} + /// Called when a "set alternate setting" control request is done on the interface. fn set_alternate_setting(&mut self, alternate_setting: u8) { let _ = alternate_setting; } diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index 497f0319..ae38e26c 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,3 +1,5 @@ +//! Utilities for writing USB descriptors. + use crate::builder::Config; use crate::driver::EndpointInfo; use crate::types::*; @@ -236,7 +238,7 @@ impl<'a> DescriptorWriter<'a> { endpoint.ep_type as u8, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize - endpoint.interval, // bInterval + endpoint.interval_ms, // bInterval ], ); } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 096e8b07..2656af29 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] #![feature(type_alias_impl_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; @@ -46,10 +48,13 @@ pub enum UsbDeviceState { Configured, } +/// Error returned by [`UsbDevice::remote_wakeup`]. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RemoteWakeupError { + /// The USB device is not suspended, or remote wakeup was not enabled. InvalidState, + /// The underlying driver doesn't support remote wakeup. Unsupported, } @@ -65,6 +70,7 @@ pub const CONFIGURATION_NONE: u8 = 0; /// The bConfiguration value for the single configuration supported by this device. pub const CONFIGURATION_VALUE: u8 = 1; +/// Maximum interface count, configured at compile time. pub const MAX_INTERFACE_COUNT: usize = 4; const STRING_INDEX_MANUFACTURER: u8 = 1; @@ -100,6 +106,7 @@ struct Interface<'d> { num_strings: u8, } +/// Main struct for the USB device stack. pub struct UsbDevice<'d, D: Driver<'d>> { control_buf: &'d mut [u8], control: D::ControlPipe, @@ -489,7 +496,6 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { .unwrap(); // TODO check it is valid (not out of range) - // TODO actually enable/disable endpoints. if let Some(handler) = &mut iface.handler { handler.set_alternate_setting(new_altsetting); diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index aeab063d..1743e61f 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -1,3 +1,5 @@ +//! USB types. + /// A handle for a USB interface that contains its number. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] From b5cf332cc076a0de11ce6a0563a2235c9e57eb5c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 1 Feb 2023 00:48:33 +0100 Subject: [PATCH 16/95] nrf: docs. --- embassy-hal-common/src/macros.rs | 6 + .../src/macros/cortex_m_interrupt_declare.rs | 3 + embassy-nrf/README.md | 58 ++++++++++ embassy-nrf/src/buffered_uarte.rs | 9 +- embassy-nrf/src/chips/nrf5340_app.rs | 1 + embassy-nrf/src/chips/nrf5340_net.rs | 1 + embassy-nrf/src/chips/nrf9160.rs | 1 + embassy-nrf/src/gpio.rs | 2 +- embassy-nrf/src/gpiote.rs | 53 +++++++-- embassy-nrf/src/i2s.rs | 109 +++++++++++++----- embassy-nrf/src/lib.rs | 46 +------- embassy-nrf/src/nvmc.rs | 2 +- embassy-nrf/src/pdm.rs | 14 ++- embassy-nrf/src/ppi/dppi.rs | 3 + embassy-nrf/src/ppi/mod.rs | 2 +- embassy-nrf/src/ppi/ppi.rs | 2 +- embassy-nrf/src/pwm.rs | 22 +++- embassy-nrf/src/qdec.rs | 37 +++++- embassy-nrf/src/qspi.rs | 42 +++++++ embassy-nrf/src/rng.rs | 6 +- embassy-nrf/src/saadc.rs | 56 +++++---- embassy-nrf/src/spim.rs | 33 ++++-- embassy-nrf/src/spis.rs | 36 ++++-- embassy-nrf/src/temp.rs | 5 +- embassy-nrf/src/timer.rs | 36 +++++- embassy-nrf/src/twim.rs | 73 +++++++++--- embassy-nrf/src/twis.rs | 57 +++++++-- embassy-nrf/src/uarte.rs | 76 ++++++++---- embassy-nrf/src/usb.rs | 90 ++++++++++----- embassy-nrf/src/wdt.rs | 8 +- examples/nrf52840/Cargo.toml | 2 +- examples/nrf52840/src/bin/i2s_effect.rs | 6 +- examples/nrf52840/src/bin/i2s_monitor.rs | 6 +- examples/nrf52840/src/bin/i2s_waveform.rs | 6 +- examples/nrf52840/src/bin/saadc_continuous.rs | 4 +- examples/nrf52840/src/bin/usb_ethernet.rs | 6 +- examples/nrf52840/src/bin/usb_hid_keyboard.rs | 4 +- examples/nrf52840/src/bin/usb_hid_mouse.rs | 4 +- examples/nrf52840/src/bin/usb_serial.rs | 6 +- .../nrf52840/src/bin/usb_serial_multitask.rs | 6 +- 40 files changed, 694 insertions(+), 245 deletions(-) create mode 100644 embassy-nrf/README.md diff --git a/embassy-hal-common/src/macros.rs b/embassy-hal-common/src/macros.rs index 7af85f78..da791313 100644 --- a/embassy-hal-common/src/macros.rs +++ b/embassy-hal-common/src/macros.rs @@ -1,10 +1,12 @@ #[macro_export] macro_rules! peripherals { ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// Types for the peripheral singletons. pub mod peripherals { $( $(#[$cfg])? #[allow(non_camel_case_types)] + #[doc = concat!(stringify!($name), " peripheral")] pub struct $name { _private: () } $(#[$cfg])? @@ -25,9 +27,13 @@ macro_rules! peripherals { )* } + /// Struct containing all the peripheral singletons. + /// + /// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]. #[allow(non_snake_case)] pub struct Peripherals { $( + #[doc = concat!(stringify!($name), " peripheral")] $(#[$cfg])? pub $name: peripherals::$name, )* diff --git a/embassy-macros/src/macros/cortex_m_interrupt_declare.rs b/embassy-macros/src/macros/cortex_m_interrupt_declare.rs index ab61ad5d..ebbb47cd 100644 --- a/embassy-macros/src/macros/cortex_m_interrupt_declare.rs +++ b/embassy-macros/src/macros/cortex_m_interrupt_declare.rs @@ -6,7 +6,10 @@ pub fn run(name: syn::Ident) -> Result { let name_interrupt = format_ident!("{}", name); let name_handler = format!("__EMBASSY_{}_HANDLER", name); + let doc = format!("{} interrupt singleton.", name); + let result = quote! { + #[doc = #doc] #[allow(non_camel_case_types)] pub struct #name_interrupt(()); unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt { diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md new file mode 100644 index 00000000..a31cfae6 --- /dev/null +++ b/embassy-nrf/README.md @@ -0,0 +1,58 @@ +# Embassy nRF HAL + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. + +The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs +for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to +complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. + +## EasyDMA considerations + +On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting +with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI). +However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust +slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: + +```no_run +// As we pass a slice to the function whose contents will not ever change, +// the compiler writes it into the flash and thus the pointer to it will +// reference static memory. Since EasyDMA requires slices to reside in RAM, +// this function call will fail. +let result = spim.write_from_ram(&[1, 2, 3]); +assert_eq!(result, Err(Error::BufferNotInRAM)); + +// The data is still static and located in flash. However, since we are assigning +// it to a variable, the compiler will load it into memory. Passing a reference to the +// variable will yield a pointer that references dynamic memory, thus making EasyDMA happy. +// This function call succeeds. +let data = [1, 2, 3]; +let result = spim.write_from_ram(&data); +assert!(result.is_ok()); +``` + +Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions: +- Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM. +- Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission. + +Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will +fail and notify you, or the more convenient versions without the suffix which are potentially a little bit +more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage +as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840). + +Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as +mutable slices always reside in RAM. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index ea25236f..112f084c 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -1,4 +1,4 @@ -//! Async buffered UART +//! Async buffered UART driver. //! //! WARNING!!! The functionality provided here is intended to be used only //! in situations where hardware flow control are available i.e. CTS and RTS. @@ -69,7 +69,7 @@ struct StateInner<'d, U: UarteInstance, T: TimerInstance> { tx_waker: WakerRegistration, } -/// Interface to a UARTE instance +/// Buffered UARTE driver. pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { inner: RefCell>>, } @@ -199,6 +199,9 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { }); } + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. pub fn split<'u>(&'u mut self) -> (BufferedUarteRx<'u, 'd, U, T>, BufferedUarteTx<'u, 'd, U, T>) { (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) } @@ -320,10 +323,12 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { } } +/// Reader part of the buffered UARTE driver. pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> { inner: &'u BufferedUarte<'d, U, T>, } +/// Writer part of the buffered UARTE driver. pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { inner: &'u BufferedUarte<'d, U, T>, } diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index 4575f09f..c600fcbf 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -1,3 +1,4 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index 54827238..a46583ec 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -1,3 +1,4 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { diff --git a/embassy-nrf/src/chips/nrf9160.rs b/embassy-nrf/src/chips/nrf9160.rs index 472ee677..e1509ddd 100644 --- a/embassy-nrf/src/chips/nrf9160.rs +++ b/embassy-nrf/src/chips/nrf9160.rs @@ -1,3 +1,4 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index 6ef5033e..895ab934 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -1,4 +1,4 @@ -//! General purpose input/output for nRF. +//! General purpose input/output (GPIO) driver. #![macro_use] use core::convert::Infallible; diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 7467dbc6..e1816eb9 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -1,3 +1,5 @@ +//! GPIO task/event (GPIOTE) driver. + use core::convert::Infallible; use core::future::{poll_fn, Future}; use core::task::{Context, Poll}; @@ -11,29 +13,38 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::ppi::{Event, Task}; use crate::{interrupt, pac, peripherals}; -pub const CHANNEL_COUNT: usize = 8; +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 8; #[cfg(any(feature = "nrf52833", feature = "nrf52840"))] -pub const PIN_COUNT: usize = 48; +const PIN_COUNT: usize = 48; #[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))] -pub const PIN_COUNT: usize = 32; +const PIN_COUNT: usize = 32; #[allow(clippy::declare_interior_mutable_const)] const NEW_AW: AtomicWaker = AtomicWaker::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; +/// Polarity for listening to events for GPIOTE input channels. pub enum InputChannelPolarity { + /// Don't listen for any pin changes. None, + /// Listen for high to low changes. HiToLo, + /// Listen for low to high changes. LoToHi, + /// Listen for any change, either low to high or high to low. Toggle, } -/// Polarity of the `task out` operation. +/// Polarity of the OUT task operation for GPIOTE output channels. pub enum OutputChannelPolarity { + /// Set the pin high. Set, + /// Set the pin low. Clear, + /// Toggle the pin. Toggle, } @@ -162,6 +173,7 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> { } impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { + /// Create a new GPIOTE input channel driver. pub fn new(ch: impl Peripheral

+ 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { into_ref!(ch); @@ -188,6 +200,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { InputChannel { ch, pin } } + /// Asynchronously wait for an event in this channel. pub async fn wait(&self) { let g = regs(); let num = self.ch.number(); @@ -231,6 +244,7 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> { } impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { + /// Create a new GPIOTE output channel driver. pub fn new(ch: impl Peripheral

+ 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { into_ref!(ch); let g = regs(); @@ -258,20 +272,20 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { OutputChannel { ch, _pin: pin } } - /// Triggers `task out` (as configured with task_out_polarity, defaults to Toggle). + /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). pub fn out(&self) { let g = regs(); g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) }); } - /// Triggers `task set` (set associated pin high). + /// Triggers the SET task (set associated pin high). #[cfg(not(feature = "nrf51"))] pub fn set(&self) { let g = regs(); g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) }); } - /// Triggers `task clear` (set associated pin low). + /// Triggers the CLEAR task (set associated pin low). #[cfg(not(feature = "nrf51"))] pub fn clear(&self) { let g = regs(); @@ -336,48 +350,58 @@ impl<'a> Future for PortInputFuture<'a> { } impl<'d, T: GpioPin> Input<'d, T> { + /// Wait until the pin is high. If it is already high, return immediately. pub async fn wait_for_high(&mut self) { self.pin.wait_for_high().await } + /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { self.pin.wait_for_low().await } + /// Wait for the pin to undergo a transition from low to high. pub async fn wait_for_rising_edge(&mut self) { self.pin.wait_for_rising_edge().await } + /// Wait for the pin to undergo a transition from high to low. pub async fn wait_for_falling_edge(&mut self) { self.pin.wait_for_falling_edge().await } + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { self.pin.wait_for_any_edge().await } } impl<'d, T: GpioPin> Flex<'d, T> { + /// Wait until the pin is high. If it is already high, return immediately. pub async fn wait_for_high(&mut self) { self.pin.conf().modify(|_, w| w.sense().high()); PortInputFuture::new(&mut self.pin).await } + /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { self.pin.conf().modify(|_, w| w.sense().low()); PortInputFuture::new(&mut self.pin).await } + /// Wait for the pin to undergo a transition from low to high. pub async fn wait_for_rising_edge(&mut self) { self.wait_for_low().await; self.wait_for_high().await; } + /// Wait for the pin to undergo a transition from high to low. pub async fn wait_for_falling_edge(&mut self) { self.wait_for_high().await; self.wait_for_low().await; } + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { if self.is_high() { self.pin.conf().modify(|_, w| w.sense().low()); @@ -394,8 +418,17 @@ mod sealed { pub trait Channel {} } +/// GPIOTE channel trait. +/// +/// Implemented by all GPIOTE channels. pub trait Channel: sealed::Channel + Sized { + /// Get the channel number. fn number(&self) -> usize; + + /// Convert this channel to a type-erased `AnyChannel`. + /// + /// This allows using several channels in situations that might require + /// them to be the same type, like putting them in an array. fn degrade(self) -> AnyChannel { AnyChannel { number: self.number() as u8, @@ -403,6 +436,12 @@ pub trait Channel: sealed::Channel + Sized { } } +/// Type-erased channel. +/// +/// Obtained by calling `Channel::degrade`. +/// +/// This allows using several channels in situations that might require +/// them to be the same type, like putting them in an array. pub struct AnyChannel { number: u8, } diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 7e950775..770df7c8 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -1,6 +1,6 @@ -#![macro_use] +//! Inter-IC Sound (I2S) driver. -//! Support for I2S audio +#![macro_use] use core::future::poll_fn; use core::marker::PhantomData; @@ -19,16 +19,23 @@ use crate::pac::i2s::RegisterBlock; use crate::util::{slice_in_ram_or, slice_ptr_parts}; use crate::{Peripheral, EASY_DMA_SIZE}; +/// Type alias for `MultiBuffering` with 2 buffers. pub type DoubleBuffering = MultiBuffering; +/// I2S transfer error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// The buffer is too long. BufferTooLong, + /// The buffer is empty. BufferZeroLength, - BufferNotInDataMemory, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// The buffer address is not aligned. BufferMisaligned, + /// The buffer length is not a multiple of the alignment. BufferLengthMisaligned, } @@ -36,34 +43,16 @@ pub enum Error { #[derive(Clone)] #[non_exhaustive] pub struct Config { + /// Sample width pub sample_width: SampleWidth, + /// Alignment pub align: Align, + /// Sample format pub format: Format, + /// Channel configuration. pub channels: Channels, } -impl Config { - pub fn sample_width(mut self, sample_width: SampleWidth) -> Self { - self.sample_width = sample_width; - self - } - - pub fn align(mut self, align: Align) -> Self { - self.align = align; - self - } - - pub fn format(mut self, format: Format) -> Self { - self.format = format; - self - } - - pub fn channels(mut self, channels: Channels) -> Self { - self.channels = channels; - self - } -} - impl Default for Config { fn default() -> Self { Self { @@ -75,7 +64,7 @@ impl Default for Config { } } -/// I2S Mode +/// I2S clock configuration. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct MasterClock { freq: MckFreq, @@ -83,12 +72,14 @@ pub struct MasterClock { } impl MasterClock { + /// Create a new `MasterClock`. pub fn new(freq: MckFreq, ratio: Ratio) -> Self { Self { freq, ratio } } } impl MasterClock { + /// Get the sample rate for this clock configuration. pub fn sample_rate(&self) -> u32 { self.freq.to_frequency() / self.ratio.to_divisor() } @@ -97,18 +88,31 @@ impl MasterClock { /// Master clock generator frequency. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum MckFreq { + /// 32 Mhz / 8 = 4000.00 kHz _32MDiv8, + /// 32 Mhz / 10 = 3200.00 kHz _32MDiv10, + /// 32 Mhz / 11 = 2909.09 kHz _32MDiv11, + /// 32 Mhz / 15 = 2133.33 kHz _32MDiv15, + /// 32 Mhz / 16 = 2000.00 kHz _32MDiv16, + /// 32 Mhz / 21 = 1523.81 kHz _32MDiv21, + /// 32 Mhz / 23 = 1391.30 kHz _32MDiv23, + /// 32 Mhz / 30 = 1066.67 kHz _32MDiv30, + /// 32 Mhz / 31 = 1032.26 kHz _32MDiv31, + /// 32 Mhz / 32 = 1000.00 kHz _32MDiv32, + /// 32 Mhz / 42 = 761.90 kHz _32MDiv42, + /// 32 Mhz / 63 = 507.94 kHz _32MDiv63, + /// 32 Mhz / 125 = 256.00 kHz _32MDiv125, } @@ -146,14 +150,23 @@ impl From for usize { /// #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Ratio { + /// Divide by 32 _32x, + /// Divide by 48 _48x, + /// Divide by 64 _64x, + /// Divide by 96 _96x, + /// Divide by 128 _128x, + /// Divide by 192 _192x, + /// Divide by 256 _256x, + /// Divide by 384 _384x, + /// Divide by 512 _512x, } @@ -165,6 +178,7 @@ impl Ratio { usize::from(*self) as u8 } + /// Return the divisor for this ratio pub fn to_divisor(&self) -> u32 { Self::RATIOS[usize::from(*self)] } @@ -183,11 +197,17 @@ impl From for usize { /// For custom master clock configuration, please refer to [MasterClock]. #[derive(Clone, Copy)] pub enum ApproxSampleRate { + /// 11025 Hz _11025, + /// 16000 Hz _16000, + /// 22050 Hz _22050, + /// 32000 Hz _32000, + /// 44100 Hz _44100, + /// 48000 Hz _48000, } @@ -211,6 +231,7 @@ impl From for MasterClock { } impl ApproxSampleRate { + /// Get the sample rate as an integer. pub fn sample_rate(&self) -> u32 { MasterClock::from(*self).sample_rate() } @@ -223,20 +244,32 @@ impl ApproxSampleRate { /// For custom master clock configuration, please refer to [Mode]. #[derive(Clone, Copy)] pub enum ExactSampleRate { + /// 8000 Hz _8000, + /// 10582 Hz _10582, + /// 12500 Hz _12500, + /// 15625 Hz _15625, + /// 15873 Hz _15873, + /// 25000 Hz _25000, + /// 31250 Hz _31250, + /// 50000 Hz _50000, + /// 62500 Hz _62500, + /// 100000 Hz _100000, + /// 125000 Hz _125000, } impl ExactSampleRate { + /// Get the sample rate as an integer. pub fn sample_rate(&self) -> u32 { MasterClock::from(*self).sample_rate() } @@ -263,8 +296,11 @@ impl From for MasterClock { /// Sample width. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SampleWidth { + /// 8 bit samples. _8bit, + /// 16 bit samples. _16bit, + /// 24 bit samples. _24bit, } @@ -277,7 +313,9 @@ impl From for u8 { /// Channel used for the most significant sample value in a frame. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Align { + /// Left-align samples. Left, + /// Right-align samples. Right, } @@ -293,7 +331,9 @@ impl From for bool { /// Frame format. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Format { + /// I2S frame format I2S, + /// Aligned frame format Aligned, } @@ -309,8 +349,11 @@ impl From for bool { /// Channels #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Channels { + /// Stereo (2 channels). Stereo, + /// Mono, left channel only. MonoLeft, + /// Mono, right channel only. MonoRight, } @@ -320,7 +363,7 @@ impl From for u8 { } } -/// Interface to the I2S peripheral using EasyDMA to offload the transmission and reception workload. +/// I2S driver. pub struct I2S<'d, T: Instance> { i2s: PeripheralRef<'d, T>, irq: PeripheralRef<'d, T::Interrupt>, @@ -566,7 +609,7 @@ impl<'d, T: Instance> I2S<'d, T> { { trace!("SEND: {}", buffer_ptr as *const S as u32); - slice_in_ram_or(buffer_ptr, Error::BufferNotInDataMemory)?; + slice_in_ram_or(buffer_ptr, Error::BufferNotInRAM)?; compiler_fence(Ordering::SeqCst); @@ -1003,7 +1046,10 @@ impl Device { /// Sample details pub trait Sample: Sized + Copy + Default { + /// Width of this sample type. const WIDTH: usize; + + /// Scale of this sample. const SCALE: Self; } @@ -1022,12 +1068,13 @@ impl Sample for i32 { const SCALE: Self = 1 << (Self::WIDTH - 1); } -/// A 4-bytes aligned buffer. +/// A 4-bytes aligned buffer. Needed for DMA access. #[derive(Clone, Copy)] #[repr(align(4))] pub struct AlignedBuffer([T; N]); impl AlignedBuffer { + /// Create a new `AlignedBuffer`. pub fn new(array: [T; N]) -> Self { Self(array) } @@ -1052,12 +1099,14 @@ impl DerefMut for AlignedBuffer { } } +/// Set of multiple buffers, for multi-buffering transfers. pub struct MultiBuffering { buffers: [AlignedBuffer; NB], index: usize, } impl MultiBuffering { + /// Create a new `MultiBuffering`. pub fn new() -> Self { assert!(NB > 1); Self { @@ -1119,7 +1168,9 @@ pub(crate) mod sealed { } } +/// I2S peripehral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 1dd0e790..20e70a24 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -1,53 +1,11 @@ -//! # Embassy nRF HAL -//! -//! HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. -//! -//! The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs -//! for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to -//! complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. -//! -//! ## EasyDMA considerations -//! -//! On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting -//! with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI). -//! However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust -//! slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: -//! -//! ```no_run -//! // As we pass a slice to the function whose contents will not ever change, -//! // the compiler writes it into the flash and thus the pointer to it will -//! // reference static memory. Since EasyDMA requires slices to reside in RAM, -//! // this function call will fail. -//! let result = spim.write_from_ram(&[1, 2, 3]); -//! assert_eq!(result, Err(Error::DMABufferNotInDataMemory)); -//! -//! // The data is still static and located in flash. However, since we are assigning -//! // it to a variable, the compiler will load it into memory. Passing a reference to the -//! // variable will yield a pointer that references dynamic memory, thus making EasyDMA happy. -//! // This function call succeeds. -//! let data = [1, 2, 3]; -//! let result = spim.write_from_ram(&data); -//! assert!(result.is_ok()); -//! ``` -//! -//! Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions: -//! - Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM. -//! - Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission. -//! -//! Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will -//! fail and notify you, or the more convenient versions without the suffix which are potentially a little bit -//! more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage -//! as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840). -//! -//! Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as -//! mutable slices always reside in RAM. - #![no_std] #![cfg_attr( feature = "nightly", feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections) )] #![cfg_attr(feature = "nightly", allow(incomplete_features))] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] #[cfg(not(any( feature = "nrf51", diff --git a/embassy-nrf/src/nvmc.rs b/embassy-nrf/src/nvmc.rs index 405ea317..c1ffa31a 100644 --- a/embassy-nrf/src/nvmc.rs +++ b/embassy-nrf/src/nvmc.rs @@ -1,4 +1,4 @@ -//! Non-Volatile Memory Controller (NVMC) module. +//! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver. use core::{ptr, slice}; diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index b7c7022c..54feca4c 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -1,4 +1,4 @@ -//! PDM mirophone interface +//! Pulse Density Modulation (PDM) mirophone driver. use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; @@ -22,12 +22,16 @@ pub struct Pdm<'d> { phantom: PhantomData<&'d PDM>, } +/// PDM error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Buffer is too long. BufferTooLong, + /// Buffer is empty BufferZeroLength, + /// PDM is not running NotRunning, } @@ -119,6 +123,7 @@ impl<'d> Pdm<'d> { r.events_started.reset(); } + /// Sample data into the given buffer. pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> { if buffer.len() == 0 { return Err(Error::BufferZeroLength); @@ -215,14 +220,21 @@ impl Default for Config { } } +/// PDM operation mode. #[derive(PartialEq)] pub enum OperationMode { + /// Mono (1 channel) Mono, + /// Stereo (2 channels) Stereo, } + +/// PDM edge polarity #[derive(PartialEq)] pub enum Edge { + /// Left edge is rising LeftRising, + /// Left edge is falling LeftFalling, } diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs index de856c0c..0908cd7b 100644 --- a/embassy-nrf/src/ppi/dppi.rs +++ b/embassy-nrf/src/ppi/dppi.rs @@ -11,12 +11,14 @@ fn regs() -> &'static pac::dppic::RegisterBlock { } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { + /// Configure PPI channel to trigger `task` on `event`. pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event, task: Task) -> Self { Ppi::new_many_to_many(ch, [event], [task]) } } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { + /// Configure PPI channel to trigger both `task1` and `task2` on `event`. pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event, task1: Task, task2: Task) -> Self { Ppi::new_many_to_many(ch, [event], [task1, task2]) } @@ -25,6 +27,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { + /// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires. pub fn new_many_to_many( ch: impl Peripheral

+ 'd, events: [Event; EVENT_COUNT], diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index 8f5ed14c..b76eccf0 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -1,6 +1,6 @@ #![macro_use] -//! HAL interface for the PPI and DPPI peripheral. +//! Programmable Peripheral Interconnect (PPI/DPPI) driver. //! //! The (Distributed) Programmable Peripheral Interconnect interface allows for an autonomous interoperability //! between peripherals through their events and tasks. There are fixed PPI channels and fully diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs index 19abc4e1..a96ab50b 100644 --- a/embassy-nrf/src/ppi/ppi.rs +++ b/embassy-nrf/src/ppi/ppi.rs @@ -48,7 +48,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { #[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { - /// Configure PPI channel to trigger `task1` and `task2` on `event`. + /// Configure PPI channel to trigger both `task1` and `task2` on `event`. pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event, task1: Task, task2: Task) -> Self { into_ref!(ch); diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 5f750a91..708f2310 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -1,3 +1,5 @@ +//! Pulse Width Modulation (PWM) driver. + #![macro_use] use core::sync::atomic::{compiler_fence, Ordering}; @@ -32,6 +34,7 @@ pub struct SequencePwm<'d, T: Instance> { ch3: Option>, } +/// PWM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -41,7 +44,7 @@ pub enum Error { /// Min Sequence count is 1 SequenceTimesAtLeastOne, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, + BufferNotInRAM, } const MAX_SEQUENCE_LEN: usize = 32767; @@ -358,6 +361,7 @@ pub struct Sequence<'s> { } impl<'s> Sequence<'s> { + /// Create a new `Sequence` pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { Self { words, config } } @@ -367,7 +371,7 @@ impl<'s> Sequence<'s> { /// Takes at one sequence along with its configuration. #[non_exhaustive] pub struct SingleSequencer<'d, 's, T: Instance> { - pub sequencer: Sequencer<'d, 's, T>, + sequencer: Sequencer<'d, 's, T>, } impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { @@ -428,8 +432,8 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { let sequence0 = &self.sequence0; let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0); - slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?; - slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(sequence0.words, Error::BufferNotInRAM)?; + slice_in_ram_or(alt_sequence.words, Error::BufferNotInRAM)?; if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN { return Err(Error::SequenceTooLong); @@ -536,13 +540,21 @@ pub enum SequenceMode { /// PWM Base clock is system clock (16MHz) divided by prescaler #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Prescaler { + /// Divide by 1 Div1, + /// Divide by 2 Div2, + /// Divide by 4 Div4, + /// Divide by 8 Div8, + /// Divide by 16 Div16, + /// Divide by 32 Div32, + /// Divide by 64 Div64, + /// Divide by 128 Div128, } @@ -828,7 +840,9 @@ pub(crate) mod sealed { } } +/// PWM peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs index 253c85c3..c01babca 100644 --- a/embassy-nrf/src/qdec.rs +++ b/embassy-nrf/src/qdec.rs @@ -1,4 +1,4 @@ -//! Quadrature decoder interface +//! Quadrature decoder (QDEC) driver. use core::future::poll_fn; use core::task::Poll; @@ -12,17 +12,23 @@ use crate::interrupt::InterruptExt; use crate::peripherals::QDEC; use crate::{interrupt, pac, Peripheral}; -/// Quadrature decoder +/// Quadrature decoder driver. pub struct Qdec<'d> { _p: PeripheralRef<'d, QDEC>, } +/// QDEC config #[non_exhaustive] pub struct Config { + /// Number of samples pub num_samples: NumSamples, + /// Sample period pub period: SamplePeriod, + /// Set LED output pin polarity pub led_polarity: LedPolarity, + /// Enable/disable input debounce filters pub debounce: bool, + /// Time period the LED is switched ON prior to sampling (0..511 us). pub led_pre_usecs: u16, } @@ -41,6 +47,7 @@ impl Default for Config { static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d> Qdec<'d> { + /// Create a new QDEC. pub fn new( qdec: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -52,6 +59,7 @@ impl<'d> Qdec<'d> { Self::new_inner(qdec, irq, a.map_into(), b.map_into(), None, config) } + /// Create a new QDEC, with a pin for LED output. pub fn new_with_led( qdec: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -170,36 +178,61 @@ impl<'d> Qdec<'d> { } } +/// Sample period #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SamplePeriod { + /// 128 us _128us, + /// 256 us _256us, + /// 512 us _512us, + /// 1024 us _1024us, + /// 2048 us _2048us, + /// 4096 us _4096us, + /// 8192 us _8192us, + /// 16384 us _16384us, + /// 32 ms _32ms, + /// 65 ms _65ms, + /// 131 ms _131ms, } +/// Number of samples taken. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum NumSamples { + /// 10 samples _10smpl, + /// 40 samples _40smpl, + /// 80 samples _80smpl, + /// 120 samples _120smpl, + /// 160 samples _160smpl, + /// 200 samples _200smpl, + /// 240 samples _240smpl, + /// 280 samples _280smpl, + /// 1 sample _1smpl, } +/// LED polarity #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum LedPolarity { + /// Active high (a high output turns on the LED). ActiveHigh, + /// Active low (a low output turns on the LED). ActiveLow, } diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index ea0a1703..07a97001 100644 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -1,3 +1,5 @@ +//! Quad Serial Peripheral Interface (QSPI) flash driver. + #![macro_use] use core::future::poll_fn; @@ -15,6 +17,7 @@ pub use crate::pac::qspi::ifconfig0::{ pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode; use crate::{pac, Peripheral}; +/// Deep power-down config. pub struct DeepPowerDownConfig { /// Time required for entering DPM, in units of 16us pub enter_time: u16, @@ -22,37 +25,62 @@ pub struct DeepPowerDownConfig { pub exit_time: u16, } +/// QSPI bus frequency. pub enum Frequency { + /// 32 Mhz M32 = 0, + /// 16 Mhz M16 = 1, + /// 10.7 Mhz M10_7 = 2, + /// 8 Mhz M8 = 3, + /// 6.4 Mhz M6_4 = 4, + /// 5.3 Mhz M5_3 = 5, + /// 4.6 Mhz M4_6 = 6, + /// 4 Mhz M4 = 7, + /// 3.6 Mhz M3_6 = 8, + /// 3.2 Mhz M3_2 = 9, + /// 2.9 Mhz M2_9 = 10, + /// 2.7 Mhz M2_7 = 11, + /// 2.5 Mhz M2_5 = 12, + /// 2.3 Mhz M2_3 = 13, + /// 2.1 Mhz M2_1 = 14, + /// 2 Mhz M2 = 15, } +/// QSPI config. #[non_exhaustive] pub struct Config { + /// XIP offset. pub xip_offset: u32, + /// Opcode used for read operations. pub read_opcode: ReadOpcode, + /// Opcode used for write operations. pub write_opcode: WriteOpcode, + /// Page size for write operations. pub write_page_size: WritePageSize, + /// Configuration for deep power down. If None, deep power down is disabled. pub deep_power_down: Option, + /// QSPI bus frequency. pub frequency: Frequency, /// Value is specified in number of 16 MHz periods (62.5 ns) pub sck_delay: u8, /// Whether data is captured on the clock rising edge and data is output on a falling edge (MODE0) or vice-versa (MODE3) pub spi_mode: SpiMode, + /// Addressing mode (24-bit or 32-bit) pub address_mode: AddressMode, } @@ -72,20 +100,24 @@ impl Default for Config { } } +/// Error #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Operation address was out of bounds. OutOfBounds, // TODO add "not in data memory" error and check for it } +/// QSPI flash driver. pub struct Qspi<'d, T: Instance, const FLASH_SIZE: usize> { irq: PeripheralRef<'d, T::Interrupt>, dpm_enabled: bool, } impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { + /// Create a new QSPI driver. pub fn new( _qspi: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -183,6 +215,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { } } + /// Do a custom QSPI instruction. pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { let bomb = DropBomb::new(); @@ -198,6 +231,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } + /// Do a custom QSPI instruction, blocking version. pub fn blocking_custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; @@ -346,6 +380,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } + /// Read data from the flash memory. pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { let bomb = DropBomb::new(); @@ -357,6 +392,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } + /// Write data to the flash memory. pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { let bomb = DropBomb::new(); @@ -368,6 +404,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } + /// Erase a sector on the flash memory. pub async fn erase(&mut self, address: usize) -> Result<(), Error> { let bomb = DropBomb::new(); @@ -379,18 +416,21 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } + /// Read data from the flash memory, blocking version. pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { self.start_read(address, data)?; self.blocking_wait_ready(); Ok(()) } + /// Write data to the flash memory, blocking version. pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { self.start_write(address, data)?; self.blocking_wait_ready(); Ok(()) } + /// Erase a sector on the flash memory, blocking version. pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> { self.start_erase(address)?; self.blocking_wait_ready(); @@ -547,7 +587,9 @@ pub(crate) mod sealed { } } +/// QSPI peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs index e0caeaae..b0b3a8eb 100644 --- a/embassy-nrf/src/rng.rs +++ b/embassy-nrf/src/rng.rs @@ -1,3 +1,5 @@ +//! Random Number Generator (RNG) driver. + use core::future::poll_fn; use core::ptr; use core::sync::atomic::{AtomicPtr, Ordering}; @@ -128,10 +130,11 @@ impl<'d> Rng<'d> { /// However, this makes the generation of numbers slower. /// /// Defaults to disabled. - pub fn bias_correction(&self, enable: bool) { + pub fn set_bias_correction(&self, enable: bool) { RNG::regs().config.write(|w| w.dercen().bit(enable)) } + /// Fill the buffer with random bytes. pub async fn fill_bytes(&mut self, dest: &mut [u8]) { if dest.len() == 0 { return; // Nothing to fill @@ -175,6 +178,7 @@ impl<'d> Rng<'d> { drop(on_drop); } + /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { self.start(); diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 4592d468..2d01a3dd 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -1,3 +1,5 @@ +//! Successive Approximation Analog-to-Digital Converter (SAADC) driver. + #![macro_use] use core::future::poll_fn; @@ -20,6 +22,7 @@ use crate::ppi::{ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::{interrupt, pac, peripherals, Peripheral}; +/// SAADC error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -102,17 +105,17 @@ impl<'d> ChannelConfig<'d> { } } -/// The state of a continuously running sampler. While it reflects -/// the progress of a sampler, it also signals what should be done -/// next. For example, if the sampler has stopped then the Saadc implementation -/// can then tear down its infrastructure. +/// Value returned by the SAADC callback, deciding what happens next. #[derive(PartialEq)] -pub enum SamplerState { - Sampled, - Stopped, +pub enum CallbackResult { + /// The SAADC should keep sampling and calling the callback. + Continue, + /// The SAADC should stop sampling, and return. + Stop, } impl<'d, const N: usize> Saadc<'d, N> { + /// Create a new SAADC driver. pub fn new( saadc: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -285,7 +288,7 @@ impl<'d, const N: usize> Saadc<'d, N> { /// free the buffers from being used by the peripheral. Cancellation will /// also cause the sampling to be stopped. - pub async fn run_task_sampler( + pub async fn run_task_sampler( &mut self, timer: &mut T, ppi_ch1: &mut impl ConfigurableChannel, @@ -293,9 +296,9 @@ impl<'d, const N: usize> Saadc<'d, N> { frequency: Frequency, sample_counter: u32, bufs: &mut [[[i16; N]; N0]; 2], - sampler: S, + callback: F, ) where - S: FnMut(&[[i16; N]]) -> SamplerState, + F: FnMut(&[[i16; N]]) -> CallbackResult, { let r = Self::regs(); @@ -321,20 +324,20 @@ impl<'d, const N: usize> Saadc<'d, N> { || { sample_ppi.enable(); }, - sampler, + callback, ) .await; } - async fn run_sampler( + async fn run_sampler( &mut self, bufs: &mut [[[i16; N]; N0]; 2], sample_rate_divisor: Option, mut init: I, - mut sampler: S, + mut callback: F, ) where I: FnMut(), - S: FnMut(&[[i16; N]]) -> SamplerState, + F: FnMut(&[[i16; N]]) -> CallbackResult, { // In case the future is dropped, stop the task and wait for it to end. let on_drop = OnDrop::new(Self::stop_sampling_immediately); @@ -395,12 +398,15 @@ impl<'d, const N: usize> Saadc<'d, N> { r.events_end.reset(); r.intenset.write(|w| w.end().set()); - if sampler(&bufs[current_buffer]) == SamplerState::Sampled { - let next_buffer = 1 - current_buffer; - current_buffer = next_buffer; - } else { - return Poll::Ready(()); - }; + match callback(&bufs[current_buffer]) { + CallbackResult::Continue => { + let next_buffer = 1 - current_buffer; + current_buffer = next_buffer; + } + CallbackResult::Stop => { + return Poll::Ready(()); + } + } } if r.events_started.read().bits() != 0 { @@ -458,7 +464,7 @@ impl<'d> Saadc<'d, 1> { sample_rate_divisor: u16, sampler: S, ) where - S: FnMut(&[[i16; 1]]) -> SamplerState, + S: FnMut(&[[i16; 1]]) -> CallbackResult, { self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await; } @@ -658,6 +664,10 @@ pub(crate) mod sealed { /// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal. pub trait Input: sealed::Input + Into + Peripheral

+ Sized + 'static { + /// Convert this SAADC input to a type-erased `AnyInput`. + /// + /// This allows using several inputs in situations that might require + /// them to be the same type, like putting them in an array. fn degrade_saadc(self) -> AnyInput { AnyInput { channel: self.channel(), @@ -665,6 +675,10 @@ pub trait Input: sealed::Input + Into + Peripheral

+ Sized + } } +/// A type-erased SAADC input. +/// +/// This allows using several inputs in situations that might require +/// them to be the same type, like putting them in an array. pub struct AnyInput { channel: InputChannel, } diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index 7bb4e39f..17e43578 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -1,3 +1,5 @@ +//! Serial Peripheral Instance in master mode (SPIM) driver. + #![macro_use] use core::future::poll_fn; @@ -16,27 +18,37 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; use crate::{pac, Peripheral}; +/// SPIM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, + BufferNotInRAM, } -/// Interface for the SPIM peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// SPIM driver. pub struct Spim<'d, T: Instance> { _p: PeripheralRef<'d, T>, } +/// SPIM configuration. #[non_exhaustive] pub struct Config { + /// Frequency pub frequency: Frequency, + + /// SPI mode pub mode: Mode, + + /// Overread character. + /// + /// When doing bidirectional transfers, if the TX buffer is shorter than the RX buffer, + /// this byte will be transmitted in the MOSI line for the left-over bytes. pub orc: u8, } @@ -51,6 +63,7 @@ impl Default for Config { } impl<'d, T: Instance> Spim<'d, T> { + /// Create a new SPIM driver. pub fn new( spim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -70,6 +83,7 @@ impl<'d, T: Instance> Spim<'d, T> { ) } + /// Create a new SPIM driver, capable of TX only (MOSI only). pub fn new_txonly( spim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -81,6 +95,7 @@ impl<'d, T: Instance> Spim<'d, T> { Self::new_inner(spim, irq, sck.map_into(), None, Some(mosi.map_into()), config) } + /// Create a new SPIM driver, capable of RX only (MISO only). pub fn new_rxonly( spim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -194,7 +209,7 @@ impl<'d, T: Instance> Spim<'d, T> { } fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { - slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(tx, Error::BufferNotInRAM)?; // NOTE: RAM slice check for rx is not necessary, as a mutable // slice can only be built from data located in RAM. @@ -236,7 +251,7 @@ impl<'d, T: Instance> Spim<'d, T> { fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { match self.blocking_inner_from_ram(rx, tx) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -268,7 +283,7 @@ impl<'d, T: Instance> Spim<'d, T> { async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { match self.async_inner_from_ram(rx, tx).await { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -385,7 +400,9 @@ pub(crate) mod sealed { } } +/// SPIM peripheral instance pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } @@ -437,7 +454,7 @@ mod eh1 { match *self { Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::spi::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other, } } } diff --git a/embassy-nrf/src/spis.rs b/embassy-nrf/src/spis.rs index 44af61a1..1b743647 100644 --- a/embassy-nrf/src/spis.rs +++ b/embassy-nrf/src/spis.rs @@ -1,3 +1,5 @@ +//! Serial Peripheral Instance in slave mode (SPIS) driver. + #![macro_use] use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; @@ -14,28 +16,43 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; use crate::{pac, Peripheral}; +/// SPIS error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, + BufferNotInRAM, } -/// Interface for the SPIS peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// SPIS driver. pub struct Spis<'d, T: Instance> { _p: PeripheralRef<'d, T>, } +/// SPIS configuration. #[non_exhaustive] pub struct Config { + /// SPI mode pub mode: Mode, + + /// Overread character. + /// + /// If the master keeps clocking the bus after all the bytes in the TX buffer have + /// already been transmitted, this byte will be constantly transmitted in the MISO line. pub orc: u8, + + /// Default byte. + /// + /// This is the byte clocked out in the MISO line for ignored transactions (if the master + /// sets CSN low while the semaphore is owned by the firmware) pub def: u8, + + /// Automatically make the firmware side acquire the semaphore on transfer end. pub auto_acquire: bool, } @@ -51,6 +68,7 @@ impl Default for Config { } impl<'d, T: Instance> Spis<'d, T> { + /// Create a new SPIS driver. pub fn new( spis: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -72,6 +90,7 @@ impl<'d, T: Instance> Spis<'d, T> { ) } + /// Create a new SPIS driver, capable of TX only (MISO only). pub fn new_txonly( spis: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -92,6 +111,7 @@ impl<'d, T: Instance> Spis<'d, T> { ) } + /// Create a new SPIS driver, capable of RX only (MOSI only). pub fn new_rxonly( spis: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -212,7 +232,7 @@ impl<'d, T: Instance> Spis<'d, T> { } fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { - slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(tx, Error::BufferNotInRAM)?; // NOTE: RAM slice check for rx is not necessary, as a mutable // slice can only be built from data located in RAM. @@ -267,7 +287,7 @@ impl<'d, T: Instance> Spis<'d, T> { fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { match self.blocking_inner_from_ram(rx, tx) { Ok(n) => Ok(n), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIS tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -330,7 +350,7 @@ impl<'d, T: Instance> Spis<'d, T> { async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { match self.async_inner_from_ram(rx, tx).await { Ok(n) => Ok(n), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIS tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -468,7 +488,9 @@ pub(crate) mod sealed { } } +/// SPIS peripheral instance pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs index 7a7f61b5..5298faab 100644 --- a/embassy-nrf/src/temp.rs +++ b/embassy-nrf/src/temp.rs @@ -1,4 +1,4 @@ -//! Temperature sensor interface. +//! Builtin temperature sensor driver. use core::future::poll_fn; use core::task::Poll; @@ -12,7 +12,7 @@ use crate::interrupt::InterruptExt; use crate::peripherals::TEMP; use crate::{interrupt, pac, Peripheral}; -/// Integrated temperature sensor. +/// Builtin temperature sensor driver. pub struct Temp<'d> { _irq: PeripheralRef<'d, interrupt::TEMP>, } @@ -20,6 +20,7 @@ pub struct Temp<'d> { static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d> Temp<'d> { + /// Create a new temperature sensor driver. pub fn new(_t: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { into_ref!(_t, irq); diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs index bc871064..d1ae5723 100644 --- a/embassy-nrf/src/timer.rs +++ b/embassy-nrf/src/timer.rs @@ -1,3 +1,9 @@ +//! Timer driver. +//! +//! Important note! This driver is very low level. For most time-related use cases, like +//! "sleep for X seconds", "do something every X seconds", or measuring time, you should +//! use [`embassy-time`](https://crates.io/crates/embassy-time) instead! + #![macro_use] use core::future::poll_fn; @@ -28,9 +34,13 @@ pub(crate) mod sealed { pub trait TimerType {} } +/// Basic Timer instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } + +/// Extended timer instance. pub trait ExtendedInstance: Instance + sealed::ExtendedInstance {} macro_rules! impl_timer { @@ -61,18 +71,28 @@ macro_rules! impl_timer { }; } +/// Timer frequency #[repr(u8)] pub enum Frequency { - // I'd prefer not to prefix these with `F`, but Rust identifiers can't start with digits. + /// 16MHz F16MHz = 0, + /// 8MHz F8MHz = 1, + /// 4MHz F4MHz = 2, + /// 2MHz F2MHz = 3, + /// 1MHz F1MHz = 4, + /// 500kHz F500kHz = 5, + /// 250kHz F250kHz = 6, + /// 125kHz F125kHz = 7, + /// 62500Hz F62500Hz = 8, + /// 31250Hz F31250Hz = 9, } @@ -86,7 +106,10 @@ pub enum Frequency { pub trait TimerType: sealed::TimerType {} +/// Marker type indicating the timer driver can await expiration (it owns the timer interrupt). pub enum Awaitable {} + +/// Marker type indicating the timer driver cannot await expiration (it does not own the timer interrupt). pub enum NotAwaitable {} impl sealed::TimerType for Awaitable {} @@ -94,12 +117,14 @@ impl sealed::TimerType for NotAwaitable {} impl TimerType for Awaitable {} impl TimerType for NotAwaitable {} +/// Timer driver. pub struct Timer<'d, T: Instance, I: TimerType = NotAwaitable> { _p: PeripheralRef<'d, T>, _i: PhantomData, } impl<'d, T: Instance> Timer<'d, T, Awaitable> { + /// Create a new async-capable timer driver. pub fn new_awaitable(timer: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { into_ref!(irq); @@ -107,16 +132,17 @@ impl<'d, T: Instance> Timer<'d, T, Awaitable> { irq.unpend(); irq.enable(); - Self::new_irqless(timer) + Self::new_inner(timer) } } + impl<'d, T: Instance> Timer<'d, T, NotAwaitable> { - /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. + /// Create a `Timer` driver without an interrupt, meaning `Cc::wait` won't work. /// /// This can be useful for triggering tasks via PPI /// `Uarte` uses this internally. pub fn new(timer: impl Peripheral

+ 'd) -> Self { - Self::new_irqless(timer) + Self::new_inner(timer) } } @@ -124,7 +150,7 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. /// /// This is used by the public constructors. - fn new_irqless(timer: impl Peripheral

+ 'd) -> Self { + fn new_inner(timer: impl Peripheral

+ 'd) -> Self { into_ref!(timer); let regs = T::regs(); diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index 4eafd18c..0dcb2b0d 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -1,11 +1,7 @@ +//! I2C-compatible Two Wire Interface in master mode (TWIM) driver. + #![macro_use] -//! HAL interface to the TWIM peripheral. -//! -//! See product specification: -//! -//! - nRF52832: Section 33 -//! - nRF52840: Section 6.31 use core::future::{poll_fn, Future}; use core::sync::atomic::compiler_fence; use core::sync::atomic::Ordering::SeqCst; @@ -23,22 +19,39 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::util::{slice_in_ram, slice_in_ram_or}; use crate::{gpio, pac, Peripheral}; +/// TWI frequency #[derive(Clone, Copy)] pub enum Frequency { - #[doc = "26738688: 100 kbps"] + /// 100 kbps K100 = 26738688, - #[doc = "67108864: 250 kbps"] + /// 250 kbps K250 = 67108864, - #[doc = "104857600: 400 kbps"] + /// 400 kbps K400 = 104857600, } +/// TWIM config. #[non_exhaustive] pub struct Config { + /// Frequency pub frequency: Frequency, + + /// Enable high drive for the SDA line. pub sda_high_drive: bool, + + /// Enable internal pullup for the SDA line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub sda_pullup: bool, + + /// Enable high drive for the SCL line. pub scl_high_drive: bool, + + /// Enable internal pullup for the SCL line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub scl_pullup: bool, } @@ -54,29 +67,38 @@ impl Default for Config { } } +/// TWI error. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, + /// Data transmit failed. Transmit, + /// Data reception failed. Receive, - DMABufferNotInDataMemory, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Didn't receive an ACK bit after the address byte. Address might be wrong, or the i2c device chip might not be connected properly. AddressNack, + /// Didn't receive an ACK bit after a data byte. DataNack, + /// Overrun error. Overrun, + /// Timeout error. Timeout, } -/// Interface to a TWIM instance using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// TWI driver. pub struct Twim<'d, T: Instance> { _p: PeripheralRef<'d, T>, } impl<'d, T: Instance> Twim<'d, T> { + /// Create a new TWI driver. pub fn new( twim: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -153,7 +175,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// Set TX buffer, checking that it is in RAM and has suitable length. unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); @@ -233,7 +255,7 @@ impl<'d, T: Instance> Twim<'d, T> { return Err(Error::DataNack); } if err.overrun().is_received() { - return Err(Error::DataNack); + return Err(Error::Overrun); } Ok(()) } @@ -435,7 +457,7 @@ impl<'d, T: Instance> Twim<'d, T> { ) -> Result<(), Error> { match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -448,7 +470,7 @@ impl<'d, T: Instance> Twim<'d, T> { fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { match self.setup_write_from_ram(address, wr_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -612,6 +634,10 @@ impl<'d, T: Instance> Twim<'d, T> { // =========================================== + /// Read from an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { self.setup_read(address, buffer, true)?; self.async_wait().await; @@ -621,6 +647,10 @@ impl<'d, T: Instance> Twim<'d, T> { Ok(()) } + /// Write to an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { self.setup_write(address, buffer, true)?; self.async_wait().await; @@ -640,6 +670,11 @@ impl<'d, T: Instance> Twim<'d, T> { Ok(()) } + /// Write data to an I2C slave, then read data from the slave without + /// triggering a stop condition between the two. + /// + /// The buffers must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { self.setup_write_read(address, wr_buffer, rd_buffer, true)?; self.async_wait().await; @@ -705,7 +740,9 @@ pub(crate) mod sealed { } } +/// TWIM peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } @@ -776,7 +813,7 @@ mod eh1 { Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other, Self::Receive => embedded_hal_1::i2c::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::i2c::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::i2c::ErrorKind::Other, Self::AddressNack => { embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) } diff --git a/embassy-nrf/src/twis.rs b/embassy-nrf/src/twis.rs index 4091b017..51a70c30 100644 --- a/embassy-nrf/src/twis.rs +++ b/embassy-nrf/src/twis.rs @@ -1,11 +1,7 @@ +//! I2C-compatible Two Wire Interface in slave mode (TWIM) driver. + #![macro_use] -//! HAL interface to the TWIS peripheral. -//! -//! See product specification: -//! -//! - nRF52832: Section 33 -//! - nRF52840: Section 6.31 use core::future::{poll_fn, Future}; use core::sync::atomic::compiler_fence; use core::sync::atomic::Ordering::SeqCst; @@ -22,14 +18,37 @@ use crate::interrupt::{Interrupt, InterruptExt}; use crate::util::slice_in_ram_or; use crate::{gpio, pac, Peripheral}; +/// TWIS config. #[non_exhaustive] pub struct Config { + /// First address pub address0: u8, + + /// Second address, optional. pub address1: Option, + + /// Overread character. + /// + /// If the master keeps clocking the bus after all the bytes in the TX buffer have + /// already been transmitted, this byte will be constantly transmitted. pub orc: u8, + + /// Enable high drive for the SDA line. pub sda_high_drive: bool, + + /// Enable internal pullup for the SDA line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub sda_pullup: bool, + + /// Enable high drive for the SCL line. pub scl_high_drive: bool, + + /// Enable internal pullup for the SCL line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub scl_pullup: bool, } @@ -54,36 +73,48 @@ enum Status { Write, } +/// TWIS error. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// TX buffer was too long. RxBufferTooLong, + /// Didn't receive an ACK bit after a data byte. DataNack, + /// Bus error. Bus, - DMABufferNotInDataMemory, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Overflow Overflow, + /// Overread OverRead, + /// Timeout Timeout, } +/// Received command #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Command { + /// Read Read, + /// Write+read WriteRead(usize), + /// Write Write(usize), } -/// Interface to a TWIS instance using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// TWIS driver. pub struct Twis<'d, T: Instance> { _p: PeripheralRef<'d, T>, } impl<'d, T: Instance> Twis<'d, T> { + /// Create a new TWIS driver. pub fn new( twis: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, @@ -174,7 +205,7 @@ impl<'d, T: Instance> Twis<'d, T> { /// Set TX buffer, checking that it is in RAM and has suitable length. unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); @@ -535,7 +566,7 @@ impl<'d, T: Instance> Twis<'d, T> { fn setup_respond(&mut self, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { match self.setup_respond_from_ram(wr_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIS tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -737,7 +768,9 @@ pub(crate) mod sealed { } } +/// TWIS peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 031cf82f..48457744 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -1,8 +1,6 @@ -#![macro_use] - -//! Async UART +//! Universal Asynchronous Receiver Transmitter (UART) driver. //! -//! Async UART is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. +//! The UART driver is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. //! The [Uarte] here is useful for those use-cases where reading the UARTE peripheral is //! exclusively awaited on. If the [Uarte] is required to be awaited on with some other future, //! for example when using `futures_util::future::select`, then you should consider @@ -13,6 +11,8 @@ //! memory may be used given that buffers are passed in directly to its read and write //! methods. +#![macro_use] + use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -32,10 +32,13 @@ use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::util::slice_in_ram_or; use crate::{pac, Peripheral}; +/// UARTE config. #[derive(Clone)] #[non_exhaustive] pub struct Config { + /// Parity bit. pub parity: Parity, + /// Baud rate. pub baudrate: Baudrate, } @@ -48,31 +51,33 @@ impl Default for Config { } } +/// UART error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Buffer was too long. BufferTooLong, - DMABufferNotInDataMemory, - // TODO: add other error variants. + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, } -/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// UARTE driver. pub struct Uarte<'d, T: Instance> { tx: UarteTx<'d, T>, rx: UarteRx<'d, T>, } -/// Transmitter interface to the UARTE peripheral obtained -/// via [Uarte]::split. +/// Transmitter part of the UARTE driver. +/// +/// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteTx<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -/// Receiver interface to the UARTE peripheral obtained -/// via [Uarte]::split. +/// Receiver part of the UARTE driver. +/// +/// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteRx<'d, T: Instance> { _p: PeripheralRef<'d, T>, } @@ -165,16 +170,16 @@ impl<'d, T: Instance> Uarte<'d, T> { } } - /// Split the Uarte into a transmitter and receiver, which is - /// particularly useful when having two tasks correlating to - /// transmitting and receiving. + /// Split the Uarte into the transmitter and receiver parts. + /// + /// This is useful to concurrently transmit and receive from independent tasks. pub fn split(self) -> (UarteTx<'d, T>, UarteRx<'d, T>) { (self.tx, self.rx) } - /// Split the Uarte into a transmitter and receiver that will - /// return on idle, which is determined as the time it takes - /// for two bytes to be received. + /// Split the Uarte into the transmitter and receiver with idle support parts. + /// + /// This is useful to concurrently transmit and receive from independent tasks. pub fn split_with_idle( self, timer: impl Peripheral

+ 'd, @@ -247,10 +252,12 @@ impl<'d, T: Instance> Uarte<'d, T> { } } + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await } + /// Write all bytes in the buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await } @@ -260,10 +267,12 @@ impl<'d, T: Instance> Uarte<'d, T> { self.tx.write_from_ram(buffer).await } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buffer) } + /// Write all bytes in the buffer. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) } @@ -355,10 +364,11 @@ impl<'d, T: Instance> UarteTx<'d, T> { Self { _p: uarte } } + /// Write all bytes in the buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { match self.write_from_ram(buffer).await { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying UARTE tx buffer into RAM for DMA"); let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; ram_buf.copy_from_slice(buffer); @@ -368,12 +378,13 @@ impl<'d, T: Instance> UarteTx<'d, T> { } } + /// Same as [`write`](Self::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub async fn write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { if buffer.len() == 0 { return Ok(()); } - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); } @@ -423,10 +434,11 @@ impl<'d, T: Instance> UarteTx<'d, T> { Ok(()) } + /// Write all bytes in the buffer. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { match self.blocking_write_from_ram(buffer) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying UARTE tx buffer into RAM for DMA"); let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; ram_buf.copy_from_slice(buffer); @@ -436,12 +448,13 @@ impl<'d, T: Instance> UarteTx<'d, T> { } } + /// Same as [`write_from_ram`](Self::write_from_ram) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub fn blocking_write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { if buffer.len() == 0 { return Ok(()); } - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); } @@ -549,6 +562,7 @@ impl<'d, T: Instance> UarteRx<'d, T> { Self { _p: uarte } } + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { if buffer.len() == 0 { return Ok(()); @@ -602,6 +616,7 @@ impl<'d, T: Instance> UarteRx<'d, T> { Ok(()) } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { if buffer.len() == 0 { return Ok(()); @@ -653,6 +668,9 @@ impl<'a, T: Instance> Drop for UarteRx<'a, T> { } } +/// Receiver part of the UARTE driver, with `read_until_idle` support. +/// +/// This can be obtained via [`Uarte::split_with_idle`]. pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> { rx: UarteRx<'d, T>, timer: Timer<'d, U>, @@ -661,16 +679,21 @@ pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> { } impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.ppi_ch1.disable(); self.rx.read(buffer).await } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.ppi_ch1.disable(); self.rx.blocking_read(buffer) } + /// Read bytes until the buffer is filled, or the line becomes idle. + /// + /// Returns the amount of bytes read. pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { if buffer.len() == 0 { return Ok(0); @@ -727,6 +750,9 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { Ok(n) } + /// Read bytes until the buffer is filled, or the line becomes idle. + /// + /// Returns the amount of bytes read. pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { if buffer.len() == 0 { return Ok(0); @@ -860,7 +886,9 @@ pub(crate) mod sealed { } } +/// UARTE peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } @@ -919,7 +947,7 @@ mod eh1 { fn kind(&self) -> embedded_hal_1::serial::ErrorKind { match *self { Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::serial::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::serial::ErrorKind::Other, } } } diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index f030b909..cd142f00 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -1,3 +1,5 @@ +//! Universal Serial Bus (USB) driver. + #![macro_use] use core::future::poll_fn; @@ -24,38 +26,38 @@ static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); +/// Trait for detecting USB VBUS power. +/// /// There are multiple ways to detect USB power. The behavior /// here provides a hook into determining whether it is. -pub trait UsbSupply { +pub trait VbusDetect { + /// Report whether power is detected. + /// + /// This is indicated by the `USBREGSTATUS.VBUSDETECT` register, or the + /// `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral. fn is_usb_detected(&self) -> bool; + + /// Wait until USB power is ready. + /// + /// USB power ready is indicated by the `USBREGSTATUS.OUTPUTRDY` register, or the + /// `USBPWRRDY` event from the `POWER` peripheral. async fn wait_power_ready(&mut self) -> Result<(), ()>; } -pub struct Driver<'d, T: Instance, P: UsbSupply> { - _p: PeripheralRef<'d, T>, - alloc_in: Allocator, - alloc_out: Allocator, - usb_supply: P, -} - -/// Uses the POWER peripheral to detect when power is available -/// for USB. Unsuitable for usage with the nRF softdevice. +/// [`VbusDetect`] implementation using the native hardware POWER peripheral. +/// +/// Unsuitable for usage with the nRF softdevice, since it reserves exclusive acces +/// to POWER. In that case, use [`VbusDetectSignal`]. #[cfg(not(feature = "_nrf5340-app"))] -pub struct PowerUsb { +pub struct HardwareVbusDetect { _private: (), } -/// Can be used to signal that power is available. Particularly suited for -/// use with the nRF softdevice. -pub struct SignalledSupply { - usb_detected: AtomicBool, - power_ready: AtomicBool, -} - static POWER_WAKER: AtomicWaker = NEW_AW; #[cfg(not(feature = "_nrf5340-app"))] -impl PowerUsb { +impl HardwareVbusDetect { + /// Create a new `VbusDetectNative`. pub fn new(power_irq: impl Interrupt) -> Self { let regs = unsafe { &*pac::POWER::ptr() }; @@ -92,7 +94,7 @@ impl PowerUsb { } #[cfg(not(feature = "_nrf5340-app"))] -impl UsbSupply for PowerUsb { +impl VbusDetect for HardwareVbusDetect { fn is_usb_detected(&self) -> bool { let regs = unsafe { &*pac::POWER::ptr() }; regs.usbregstatus.read().vbusdetect().is_vbus_present() @@ -115,7 +117,20 @@ impl UsbSupply for PowerUsb { } } -impl SignalledSupply { +/// Software-backed [`VbusDetect`] implementation. +/// +/// This implementation does not interact with the hardware, it allows user code +/// to notify the power events by calling functions instead. +/// +/// This is suitable for use with the nRF softdevice, by calling the functions +/// when the softdevice reports power-related events. +pub struct SoftwareVbusDetect { + usb_detected: AtomicBool, + power_ready: AtomicBool, +} + +impl SoftwareVbusDetect { + /// Create a new `SoftwareVbusDetect`. pub fn new(usb_detected: bool, power_ready: bool) -> Self { BUS_WAKER.wake(); @@ -125,6 +140,9 @@ impl SignalledSupply { } } + /// Report whether power was detected. + /// + /// Equivalent to the `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral. pub fn detected(&self, detected: bool) { self.usb_detected.store(detected, Ordering::Relaxed); self.power_ready.store(false, Ordering::Relaxed); @@ -132,13 +150,16 @@ impl SignalledSupply { POWER_WAKER.wake(); } + /// Report when USB power is ready. + /// + /// Equivalent to the `USBPWRRDY` event from the `POWER` peripheral. pub fn ready(&self) { self.power_ready.store(true, Ordering::Relaxed); POWER_WAKER.wake(); } } -impl UsbSupply for &SignalledSupply { +impl VbusDetect for &SoftwareVbusDetect { fn is_usb_detected(&self) -> bool { self.usb_detected.load(Ordering::Relaxed) } @@ -159,7 +180,16 @@ impl UsbSupply for &SignalledSupply { } } -impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { +/// USB driver. +pub struct Driver<'d, T: Instance, P: VbusDetect> { + _p: PeripheralRef<'d, T>, + alloc_in: Allocator, + alloc_out: Allocator, + usb_supply: P, +} + +impl<'d, T: Instance, P: VbusDetect> Driver<'d, T, P> { + /// Create a new USB driver. pub fn new(usb: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, usb_supply: P) -> Self { into_ref!(usb, irq); irq.set_handler(Self::on_interrupt); @@ -225,7 +255,7 @@ impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { } } -impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> { +impl<'d, T: Instance, P: VbusDetect + 'd> driver::Driver<'d> for Driver<'d, T, P> { type EndpointOut = Endpoint<'d, T, Out>; type EndpointIn = Endpoint<'d, T, In>; type ControlPipe = ControlPipe<'d, T>; @@ -278,13 +308,14 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> } } -pub struct Bus<'d, T: Instance, P: UsbSupply> { +/// USB bus. +pub struct Bus<'d, T: Instance, P: VbusDetect> { _p: PeripheralRef<'d, T>, power_available: bool, usb_supply: P, } -impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { +impl<'d, T: Instance, P: VbusDetect> driver::Bus for Bus<'d, T, P> { async fn enable(&mut self) { let regs = T::regs(); @@ -513,7 +544,10 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { } } +/// Type-level marker for OUT endpoints. pub enum Out {} + +/// Type-level marker for IN endpoints. pub enum In {} trait EndpointDir { @@ -556,6 +590,7 @@ impl EndpointDir for Out { } } +/// USB endpoint. pub struct Endpoint<'d, T: Instance, Dir> { _phantom: PhantomData<(&'d mut T, Dir)>, info: EndpointInfo, @@ -715,6 +750,7 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { } } +/// USB control pipe. pub struct ControlPipe<'d, T: Instance> { _p: PeripheralRef<'d, T>, max_packet_size: u16, @@ -905,7 +941,9 @@ pub(crate) mod sealed { } } +/// USB peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. type Interrupt: Interrupt; } diff --git a/embassy-nrf/src/wdt.rs b/embassy-nrf/src/wdt.rs index 330ca98b..40a67442 100644 --- a/embassy-nrf/src/wdt.rs +++ b/embassy-nrf/src/wdt.rs @@ -1,4 +1,4 @@ -//! HAL interface to the WDT peripheral. +//! Watchdog Timer (WDT) driver. //! //! This HAL implements a basic watchdog timer with 1..=8 handles. //! Once the watchdog has been started, it cannot be stopped. @@ -8,6 +8,7 @@ use crate::peripherals; const MIN_TICKS: u32 = 15; +/// WDT configuration. #[non_exhaustive] pub struct Config { /// Number of 32768 Hz ticks in each watchdog period. @@ -57,13 +58,13 @@ impl Default for Config { } } -/// An interface to the Watchdog. +/// Watchdog driver. pub struct Watchdog { _private: (), } impl Watchdog { - /// Try to create a new watchdog instance from the peripheral. + /// Try to create a new watchdog driver. /// /// This function will return an error if the watchdog is already active /// with a `config` different to the requested one, or a different number of @@ -155,6 +156,7 @@ impl Watchdog { } } +/// Watchdog handle. pub struct WatchdogHandle { index: u8, } diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index bbd8a5d2..95d93987 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -14,7 +14,7 @@ embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } embedded-io = "0.4.0" diff --git a/examples/nrf52840/src/bin/i2s_effect.rs b/examples/nrf52840/src/bin/i2s_effect.rs index 3cca005b..52d46e4f 100644 --- a/examples/nrf52840/src/bin/i2s_effect.rs +++ b/examples/nrf52840/src/bin/i2s_effect.rs @@ -24,9 +24,9 @@ async fn main(_spawner: Spawner) { let sample_rate = master_clock.sample_rate(); info!("Sample rate: {}", sample_rate); - let config = Config::default() - .sample_width(SampleWidth::_16bit) - .channels(Channels::MonoLeft); + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; let irq = interrupt::take!(I2S); let buffers_out = MultiBuffering::::new(); diff --git a/examples/nrf52840/src/bin/i2s_monitor.rs b/examples/nrf52840/src/bin/i2s_monitor.rs index 48eb7d58..5ebfd954 100644 --- a/examples/nrf52840/src/bin/i2s_monitor.rs +++ b/examples/nrf52840/src/bin/i2s_monitor.rs @@ -22,9 +22,9 @@ async fn main(_spawner: Spawner) { let sample_rate = master_clock.sample_rate(); info!("Sample rate: {}", sample_rate); - let config = Config::default() - .sample_width(SampleWidth::_16bit) - .channels(Channels::MonoLeft); + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; let irq = interrupt::take!(I2S); let buffers = DoubleBuffering::::new(); diff --git a/examples/nrf52840/src/bin/i2s_waveform.rs b/examples/nrf52840/src/bin/i2s_waveform.rs index 1b0e8ebc..eda93067 100644 --- a/examples/nrf52840/src/bin/i2s_waveform.rs +++ b/examples/nrf52840/src/bin/i2s_waveform.rs @@ -23,9 +23,9 @@ async fn main(_spawner: Spawner) { let sample_rate = master_clock.sample_rate(); info!("Sample rate: {}", sample_rate); - let config = Config::default() - .sample_width(SampleWidth::_16bit) - .channels(Channels::MonoLeft); + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; let irq = interrupt::take!(I2S); let buffers = DoubleBuffering::::new(); diff --git a/examples/nrf52840/src/bin/saadc_continuous.rs b/examples/nrf52840/src/bin/saadc_continuous.rs index bb50ac65..2551d15f 100644 --- a/examples/nrf52840/src/bin/saadc_continuous.rs +++ b/examples/nrf52840/src/bin/saadc_continuous.rs @@ -5,7 +5,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_nrf::interrupt; -use embassy_nrf::saadc::{ChannelConfig, Config, Saadc, SamplerState}; +use embassy_nrf::saadc::{CallbackResult, ChannelConfig, Config, Saadc}; use embassy_nrf::timer::Frequency; use embassy_time::Duration; use {defmt_rtt as _, panic_probe as _}; @@ -61,7 +61,7 @@ async fn main(_p: Spawner) { c = 0; a = 0; } - SamplerState::Sampled + CallbackResult::Continue }, ) .await; diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index a8d53e46..699666ce 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -9,7 +9,7 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Stack, StackResources}; use embassy_nrf::rng::Rng; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac, peripherals}; use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; @@ -18,7 +18,7 @@ use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -type MyDriver = Driver<'static, peripherals::USBD, PowerUsb>; +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; macro_rules! singleton { ($val:expr) => {{ @@ -58,7 +58,7 @@ async fn main(spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); diff --git a/examples/nrf52840/src/bin/usb_hid_keyboard.rs b/examples/nrf52840/src/bin/usb_hid_keyboard.rs index 76e19871..017cac19 100644 --- a/examples/nrf52840/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -10,7 +10,7 @@ use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_futures::select::{select, Either}; use embassy_nrf::gpio::{Input, Pin, Pull}; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::signal::Signal; @@ -34,7 +34,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); diff --git a/examples/nrf52840/src/bin/usb_hid_mouse.rs b/examples/nrf52840/src/bin/usb_hid_mouse.rs index 4916a38d..a5849129 100644 --- a/examples/nrf52840/src/bin/usb_hid_mouse.rs +++ b/examples/nrf52840/src/bin/usb_hid_mouse.rs @@ -7,7 +7,7 @@ use core::mem; use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_time::{Duration, Timer}; use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; @@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); diff --git a/examples/nrf52840/src/bin/usb_serial.rs b/examples/nrf52840/src/bin/usb_serial.rs index 7c9c4184..18b6f25b 100644 --- a/examples/nrf52840/src/bin/usb_serial.rs +++ b/examples/nrf52840/src/bin/usb_serial.rs @@ -7,7 +7,7 @@ use core::mem; use defmt::{info, panic}; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; @@ -26,7 +26,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -97,7 +97,7 @@ impl From for Disconnected { } } -async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>( +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, ) -> Result<(), Disconnected> { let mut buf = [0; 64]; diff --git a/examples/nrf52840/src/bin/usb_serial_multitask.rs b/examples/nrf52840/src/bin/usb_serial_multitask.rs index 93efc2fe..3532d3f8 100644 --- a/examples/nrf52840/src/bin/usb_serial_multitask.rs +++ b/examples/nrf52840/src/bin/usb_serial_multitask.rs @@ -6,7 +6,7 @@ use core::mem; use defmt::{info, panic, unwrap}; use embassy_executor::Spawner; -use embassy_nrf::usb::{Driver, PowerUsb}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect}; use embassy_nrf::{interrupt, pac, peripherals}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; @@ -14,7 +14,7 @@ use embassy_usb::{Builder, Config, UsbDevice}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -type MyDriver = Driver<'static, peripherals::USBD, PowerUsb>; +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) { @@ -42,7 +42,7 @@ async fn main(spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); From a697f1517a9c54ba042bbf70e0b2ed762d300471 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Tue, 31 Jan 2023 17:29:34 -0600 Subject: [PATCH 17/95] Set `poll_fn` in `TaskStorage::new` --- embassy-executor/src/raw/mod.rs | 35 +++++++++++++------------------- embassy-executor/src/raw/util.rs | 6 ------ 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 183c5e6a..8cdce92e 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -46,8 +46,8 @@ pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; pub(crate) struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, - pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 - pub(crate) poll_fn: UninitCell, // Valid if STATE_SPAWNED + pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 + poll_fn: unsafe fn(TaskRef), #[cfg(feature = "integrated-timers")] pub(crate) expires_at: Cell, @@ -55,22 +55,6 @@ pub(crate) struct TaskHeader { pub(crate) timer_queue_item: timer_queue::TimerQueueItem, } -impl TaskHeader { - const fn new() -> Self { - Self { - state: AtomicU32::new(0), - run_queue_item: RunQueueItem::new(), - executor: Cell::new(ptr::null()), - poll_fn: UninitCell::uninit(), - - #[cfg(feature = "integrated-timers")] - expires_at: Cell::new(Instant::from_ticks(0)), - #[cfg(feature = "integrated-timers")] - timer_queue_item: timer_queue::TimerQueueItem::new(), - } - } -} - /// This is essentially a `&'static TaskStorage` where the type of the future has been erased. #[derive(Clone, Copy)] pub struct TaskRef { @@ -128,7 +112,17 @@ impl TaskStorage { /// Create a new TaskStorage, in not-spawned state. pub const fn new() -> Self { Self { - raw: TaskHeader::new(), + raw: TaskHeader { + state: AtomicU32::new(0), + run_queue_item: RunQueueItem::new(), + executor: Cell::new(ptr::null()), + poll_fn: Self::poll, + + #[cfg(feature = "integrated-timers")] + expires_at: Cell::new(Instant::from_ticks(0)), + #[cfg(feature = "integrated-timers")] + timer_queue_item: timer_queue::TimerQueueItem::new(), + }, future: UninitCell::uninit(), } } @@ -164,7 +158,6 @@ impl TaskStorage { unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> TaskRef { // Initialize the task - self.raw.poll_fn.write(Self::poll); self.future.write(future()); TaskRef::new(self) } @@ -405,7 +398,7 @@ impl Executor { trace::task_exec_begin(p.as_ptr() as u32); // Run the task - task.poll_fn.read()(p); + (task.poll_fn)(p); #[cfg(feature = "rtos-trace")] trace::task_exec_end(); diff --git a/embassy-executor/src/raw/util.rs b/embassy-executor/src/raw/util.rs index ed582218..2b1f6b6f 100644 --- a/embassy-executor/src/raw/util.rs +++ b/embassy-executor/src/raw/util.rs @@ -25,9 +25,3 @@ impl UninitCell { ptr::drop_in_place(self.as_mut_ptr()) } } - -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) - } -} From fb1946be7fa38eecb36711a1257f89dae3714b61 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Tue, 31 Jan 2023 17:49:18 -0600 Subject: [PATCH 18/95] Replace the pointer in `TaskHeader` with an `Option<&Executor>` --- embassy-executor/src/raw/mod.rs | 10 +++++----- embassy-executor/src/spawner.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 8cdce92e..6783c485 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -15,10 +15,10 @@ mod waker; use core::cell::Cell; use core::future::Future; +use core::mem; use core::pin::Pin; use core::ptr::NonNull; use core::task::{Context, Poll}; -use core::{mem, ptr}; use atomic_polyfill::{AtomicU32, Ordering}; use critical_section::CriticalSection; @@ -46,7 +46,7 @@ pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; pub(crate) struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, - pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 + pub(crate) executor: Cell>, poll_fn: unsafe fn(TaskRef), #[cfg(feature = "integrated-timers")] @@ -115,7 +115,7 @@ impl TaskStorage { raw: TaskHeader { state: AtomicU32::new(0), run_queue_item: RunQueueItem::new(), - executor: Cell::new(ptr::null()), + executor: Cell::new(None), poll_fn: Self::poll, #[cfg(feature = "integrated-timers")] @@ -346,7 +346,7 @@ impl Executor { /// In this case, the task's Future must be Send. This is because this is effectively /// sending the task to the executor thread. pub(super) unsafe fn spawn(&'static self, task: TaskRef) { - task.header().executor.set(self); + task.header().executor.set(Some(self)); #[cfg(feature = "rtos-trace")] trace::task_new(task.as_ptr() as u32); @@ -455,7 +455,7 @@ pub fn wake_task(task: TaskRef) { // We have just marked the task as scheduled, so enqueue it. unsafe { - let executor = &*header.executor.get(); + let executor = header.executor.get().unwrap_unchecked(); executor.enqueue(cs, task); } }) diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 650ea06c..7c0a0183 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -89,10 +89,10 @@ impl Spawner { /// /// Panics if the current executor is not an Embassy executor. pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { + poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = task.header().executor.get(); - Poll::Ready(Self::new(&*executor)) + let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + Poll::Ready(Self::new(executor)) }) .await } @@ -165,10 +165,10 @@ impl SendSpawner { /// /// Panics if the current executor is not an Embassy executor. pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { + poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = task.header().executor.get(); - Poll::Ready(Self::new(&*executor)) + let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + Poll::Ready(Self::new(executor)) }) .await } From 4a8e9cf4d9f682bfe4942559da7e76315216c377 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Tue, 31 Jan 2023 18:49:32 -0600 Subject: [PATCH 19/95] Add internal `AvailableTask` type --- embassy-executor/src/raw/mod.rs | 68 +++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 6783c485..e93e6036 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -141,25 +141,14 @@ impl TaskStorage { /// Once the task has finished running, you may spawn it again. It is allowed to spawn it /// on a different executor. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - if self.spawn_mark_used() { - return unsafe { SpawnToken::::new(self.spawn_initialize(future)) }; + let task = AvailableTask::claim(self); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } + } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() - } - - fn spawn_mark_used(&'static self) -> bool { - let state = STATE_SPAWNED | STATE_RUN_QUEUED; - self.raw - .state - .compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire) - .is_ok() - } - - unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> TaskRef { - // Initialize the task - self.future.write(future()); - TaskRef::new(self) } unsafe fn poll(p: TaskRef) { @@ -184,6 +173,27 @@ impl TaskStorage { unsafe impl Sync for TaskStorage {} +struct AvailableTask { + task: &'static TaskStorage, +} + +impl AvailableTask { + fn claim(task: &'static TaskStorage) -> Option { + task.raw + .state + .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) + .ok() + .map(|_| Self { task }) + } + + fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { + unsafe { + self.task.future.write(future()); + } + TaskRef::new(self.task) + } +} + /// Raw storage that can hold up to N tasks of the same type. /// /// This is essentially a `[TaskStorage; N]`. @@ -207,13 +217,14 @@ impl TaskPool { /// is currently free. If none is free, a "poisoned" SpawnToken is returned, /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - for task in &self.pool { - if task.spawn_mark_used() { - return unsafe { SpawnToken::::new(task.spawn_initialize(future)) }; + let task = self.pool.iter().find_map(AvailableTask::claim); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } /// Like spawn(), but allows the task to be send-spawned if the args are Send even if @@ -255,13 +266,14 @@ impl TaskPool { // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. - for task in &self.pool { - if task.spawn_mark_used() { - return SpawnToken::::new(task.spawn_initialize(future)); + let task = self.pool.iter().find_map(AvailableTask::claim); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } } From 791fbb3ca0caf81882f67caea9e71adf43496261 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Tue, 31 Jan 2023 21:42:45 -0600 Subject: [PATCH 20/95] Make `poll_fn` lazily initialized again --- embassy-executor/src/raw/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index e93e6036..42bd8226 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -47,7 +47,7 @@ pub(crate) struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, pub(crate) executor: Cell>, - poll_fn: unsafe fn(TaskRef), + poll_fn: Cell>, #[cfg(feature = "integrated-timers")] pub(crate) expires_at: Cell, @@ -116,7 +116,8 @@ impl TaskStorage { state: AtomicU32::new(0), run_queue_item: RunQueueItem::new(), executor: Cell::new(None), - poll_fn: Self::poll, + // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` + poll_fn: Cell::new(None), #[cfg(feature = "integrated-timers")] expires_at: Cell::new(Instant::from_ticks(0)), @@ -188,6 +189,7 @@ impl AvailableTask { fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { unsafe { + self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); self.task.future.write(future()); } TaskRef::new(self.task) @@ -410,7 +412,7 @@ impl Executor { trace::task_exec_begin(p.as_ptr() as u32); // Run the task - (task.poll_fn)(p); + task.poll_fn.get().unwrap_unchecked()(p); #[cfg(feature = "rtos-trace")] trace::task_exec_end(); From cb88dd285dd53a12545a0f01a6d43aaf1361b82a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 1 Feb 2023 20:54:32 +0100 Subject: [PATCH 21/95] nrf/twis: FIx doc typo --- embassy-nrf/src/twis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-nrf/src/twis.rs b/embassy-nrf/src/twis.rs index 51a70c30..c514d9f2 100644 --- a/embassy-nrf/src/twis.rs +++ b/embassy-nrf/src/twis.rs @@ -80,7 +80,7 @@ enum Status { pub enum Error { /// TX buffer was too long. TxBufferTooLong, - /// TX buffer was too long. + /// RX buffer was too long. RxBufferTooLong, /// Didn't receive an ACK bit after a data byte. DataNack, From c4cbb89fcd60375c4982a61fc3d9e5183f97d748 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 2 Feb 2023 12:51:47 +0100 Subject: [PATCH 22/95] LoRa/SX126x: adjust Rx window offset and duration Those timings open Rx time windows covering 99.7% of the one expected by the antenna while allowing 3ms for the Rx subsystem to start listening. --- embassy-lora/src/sx126x/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-lora/src/sx126x/mod.rs b/embassy-lora/src/sx126x/mod.rs index b14d422a..a9aeadfc 100644 --- a/embassy-lora/src/sx126x/mod.rs +++ b/embassy-lora/src/sx126x/mod.rs @@ -55,10 +55,10 @@ where BUS: Error + Format + 'static, { fn get_rx_window_offset_ms(&self) -> i32 { - -500 + -3 } fn get_rx_window_duration_ms(&self) -> u32 { - 800 + 1003 } } From ef4a20f67b925ffcbf83642e33b13d3605b78d46 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 2 Feb 2023 12:52:00 +0100 Subject: [PATCH 23/95] LoRa/STM32WL: adjust Rx window offset and duration Those timings open Rx time windows covering 99.7% of the one expected by the antenna while allowing 3ms for the Rx subsystem to start listening. --- embassy-lora/src/stm32wl/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs index 6ed63bf7..3d52c1cc 100644 --- a/embassy-lora/src/stm32wl/mod.rs +++ b/embassy-lora/src/stm32wl/mod.rs @@ -260,10 +260,10 @@ impl From for RadioError { impl<'d, RS> Timings for SubGhzRadio<'d, RS> { fn get_rx_window_offset_ms(&self) -> i32 { - -500 + -3 } fn get_rx_window_duration_ms(&self) -> u32 { - 3000 + 1003 } } From a432d91d82438f4ce0f17c85b9453ae79db5390d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 3 Feb 2023 06:36:10 +0100 Subject: [PATCH 24/95] PeripheralRef docs improvements. --- embassy-hal-common/src/peripheral.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/embassy-hal-common/src/peripheral.rs b/embassy-hal-common/src/peripheral.rs index f507468f..4a6b6a60 100644 --- a/embassy-hal-common/src/peripheral.rs +++ b/embassy-hal-common/src/peripheral.rs @@ -3,16 +3,17 @@ use core::ops::{Deref, DerefMut}; /// An exclusive reference to a peripheral. /// -/// This is functionally the same as a `&'a mut T`. The reason for having a -/// dedicated struct is memory efficiency: +/// This is functionally the same as a `&'a mut T`. There's a few advantages in having +/// a dedicated struct instead: /// -/// Peripheral singletons are typically either zero-sized (for concrete peripherals -/// like `PA9` or `Spi4`) or very small (for example `AnyPin` which is 1 byte). -/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. -/// PeripheralRef stores a copy of `T` instead, so it's the same size. -/// -/// but it is the size of `T` not the size -/// of a pointer. This is useful if T is a zero sized type. +/// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete +/// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte). +/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. +/// PeripheralRef stores a copy of `T` instead, so it's the same size. +/// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`, +/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic +/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes +/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization. pub struct PeripheralRef<'a, T> { inner: T, _lifetime: PhantomData<&'a mut T>, From 0bb6000e5cab0a2f2dbbb95a4a2d69a2536de88b Mon Sep 17 00:00:00 2001 From: Josh Mcguigan Date: Thu, 2 Feb 2023 21:42:42 -0800 Subject: [PATCH 25/95] stm32 gpio implement degrade to AnyPin --- embassy-stm32/src/gpio.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/embassy-stm32/src/gpio.rs b/embassy-stm32/src/gpio.rs index 5e334675..3024f1ff 100644 --- a/embassy-stm32/src/gpio.rs +++ b/embassy-stm32/src/gpio.rs @@ -28,6 +28,21 @@ impl<'d, T: Pin> Flex<'d, T> { Self { pin } } + #[inline] + pub fn degrade(mut self) -> Flex<'d, AnyPin> { + // Safety: We are about to drop the other copy of this pin, so + // this clone is safe. + let pin = unsafe { self.pin.clone_unchecked() }; + + // We don't want to run the destructor here, because that would + // deconfigure the pin. + core::mem::forget(self); + + Flex { + pin: pin.map_into::(), + } + } + /// Put the pin into input mode. #[inline] pub fn set_as_input(&mut self, pull: Pull) { @@ -286,6 +301,13 @@ impl<'d, T: Pin> Input<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Input<'d, AnyPin> { + Input { + pin: self.pin.degrade(), + } + } + #[inline] pub fn is_high(&self) -> bool { self.pin.is_high() @@ -345,6 +367,13 @@ impl<'d, T: Pin> Output<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Output<'d, AnyPin> { + Output { + pin: self.pin.degrade(), + } + } + /// Set the output as high. #[inline] pub fn set_high(&mut self) { @@ -407,6 +436,13 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Output<'d, AnyPin> { + Output { + pin: self.pin.degrade(), + } + } + #[inline] pub fn is_high(&self) -> bool { !self.pin.is_low() From 64ebb9b7fee1fec62eacc1064a7458a6e6b07048 Mon Sep 17 00:00:00 2001 From: Patrick Oppenlander Date: Mon, 6 Feb 2023 09:44:13 +1100 Subject: [PATCH 26/95] stm32/usart: implement stop_bits configuration --- embassy-stm32/src/usart/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 20f4eede..b18b646b 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -770,7 +770,14 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, multiplier: u32, enable unsafe { r.brr().write_value(regs::Brr(div)); - r.cr2().write(|_w| {}); + r.cr2().write(|w| { + w.set_stop(match config.stop_bits { + StopBits::STOP0P5 => vals::Stop::STOP0P5, + StopBits::STOP1 => vals::Stop::STOP1, + StopBits::STOP1P5 => vals::Stop::STOP1P5, + StopBits::STOP2 => vals::Stop::STOP2, + }); + }); r.cr1().write(|w| { // enable uart w.set_ue(true); From fda36fd81b25bac914e977654beecdb41aaabbb3 Mon Sep 17 00:00:00 2001 From: Patrick Oppenlander Date: Mon, 6 Feb 2023 10:12:10 +1100 Subject: [PATCH 27/95] stm32/usart: fix LPUART clock multiplier According to RM0351 Rev 9 (L4) and RM0399 Rev 3 (H7): baud = (256 * clock) / LPUARTDIV --- 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 20f4eede..121699c7 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -1148,7 +1148,7 @@ macro_rules! impl_lpuart { foreach_interrupt!( ($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => { - impl_lpuart!($inst, $irq, 255); + impl_lpuart!($inst, $irq, 256); }; ($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => { From e3174d7a998ead0fbe5ddadf59914cc16a561a44 Mon Sep 17 00:00:00 2001 From: Ralf Date: Sun, 5 Feb 2023 20:17:53 +0100 Subject: [PATCH 28/95] STM32 SPI: Set clk-pin pull-up/-down to match spi clock polarity RM0394: 40.4.6 Communication formats ... The idle state of SCK must correspond to the polarity selected in the SPIx_CR1 register (by pulling up SCK if CPOL=1 or pulling down SCK if CPOL=0). --- embassy-stm32/src/spi/mod.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 5b81c791..e5ba746e 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -10,7 +10,7 @@ pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MO use self::sealed::WordSize; use crate::dma::{slice_ptr_parts, Transfer}; use crate::gpio::sealed::{AFType, Pin as _}; -use crate::gpio::AnyPin; +use crate::gpio::{AnyPin, Pull}; use crate::pac::spi::{regs, vals, Spi as Regs}; use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -93,8 +93,14 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { config: Config, ) -> Self { into_ref!(peri, sck, mosi, miso); + + let sck_pull_mode = match config.mode.polarity { + Polarity::IdleLow => Pull::Down, + Polarity::IdleHigh => Pull::Up, + }; + unsafe { - sck.set_as_af(sck.af_num(), AFType::OutputPushPull); + sck.set_as_af_pull(sck.af_num(), AFType::OutputPushPull, sck_pull_mode); sck.set_speed(crate::gpio::Speed::VeryHigh); mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); mosi.set_speed(crate::gpio::Speed::VeryHigh); From 218f8e049096341c459de8d39146d59117d20d69 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 12:17:37 +0100 Subject: [PATCH 29/95] fix(stm32): Align FMC with new versions from stm32-data --- embassy-stm32/src/fmc/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc/mod.rs index 856a4adc..0c546104 100644 --- a/embassy-stm32/src/fmc/mod.rs +++ b/embassy-stm32/src/fmc/mod.rs @@ -27,9 +27,13 @@ where } fn memory_controller_enable(&mut self) { - // The FMCEN bit of the FMC_BCR2..4 registers is don’t - // care. It is only enabled through the FMC_BCR1 register. - unsafe { T::regs().bcr1().modify(|r| r.set_fmcen(true)) }; + // fmc v1 and v2 does not have the fmcen bit + // fsmc v1 and v2 does not have the fmcen bit + // This is a "not" because it is expected that all future versions have this bit + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x3, fsmc_v2x1)))] + unsafe { + T::regs().bcr1().modify(|r| r.set_fmcen(true)) + }; } fn source_clock_hz(&self) -> u32 { From 36ca18132dd525bc2a3cdac0246834a2aae07ca9 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 12:35:59 +0100 Subject: [PATCH 30/95] Update stm32-data --- stm32-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stm32-data b/stm32-data index 96decdd6..fe4a068e 160000 --- a/stm32-data +++ b/stm32-data @@ -1 +1 @@ -Subproject commit 96decdd6114d78813c1f748fb878a45e1b03bf73 +Subproject commit fe4a068eaf1b39c60eddea7d74c90182b44c5d08 From 494a76a0f1be2fb35000c7a088f9bda21c73ad9e Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 14:14:47 +0100 Subject: [PATCH 31/95] React to updated fsmc versions --- embassy-stm32/src/fmc/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc/mod.rs index 0c546104..13c16af5 100644 --- a/embassy-stm32/src/fmc/mod.rs +++ b/embassy-stm32/src/fmc/mod.rs @@ -28,9 +28,9 @@ where fn memory_controller_enable(&mut self) { // fmc v1 and v2 does not have the fmcen bit - // fsmc v1 and v2 does not have the fmcen bit + // fsmc v1, v2 and v3 does not have the fmcen bit // This is a "not" because it is expected that all future versions have this bit - #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x3, fsmc_v2x1)))] + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v2x3, fsmc_v3x1)))] unsafe { T::regs().bcr1().modify(|r| r.set_fmcen(true)) }; From 562432ad8b9380cff7a536105b9877cdd8044870 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 14:16:13 +0100 Subject: [PATCH 32/95] Update stm32-data --- stm32-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stm32-data b/stm32-data index fe4a068e..ae8c8ed9 160000 --- a/stm32-data +++ b/stm32-data @@ -1 +1 @@ -Subproject commit fe4a068eaf1b39c60eddea7d74c90182b44c5d08 +Subproject commit ae8c8ed9dbde5eca7c5c74bd926f7ce77526dae1 From e4dc473e04ba2981cffd16ce4348f0646cae341d Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 14:46:36 +0100 Subject: [PATCH 33/95] Update stm32-data --- stm32-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stm32-data b/stm32-data index ae8c8ed9..cc93f9d1 160000 --- a/stm32-data +++ b/stm32-data @@ -1 +1 @@ -Subproject commit ae8c8ed9dbde5eca7c5c74bd926f7ce77526dae1 +Subproject commit cc93f9d10395077770bebefb6b9488e06b0e5811 From 1b6aae9ddea09bf961d9cbe4a976f3021749e4ac Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 15:06:16 +0100 Subject: [PATCH 34/95] Also exclude fsmc_v1x3 --- embassy-stm32/src/fmc/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc/mod.rs index 13c16af5..4d48721d 100644 --- a/embassy-stm32/src/fmc/mod.rs +++ b/embassy-stm32/src/fmc/mod.rs @@ -30,7 +30,7 @@ where // fmc v1 and v2 does not have the fmcen bit // fsmc v1, v2 and v3 does not have the fmcen bit // This is a "not" because it is expected that all future versions have this bit - #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v2x3, fsmc_v3x1)))] + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1)))] unsafe { T::regs().bcr1().modify(|r| r.set_fmcen(true)) }; From 7b11e339bd9ce69f114778e1401cbe8279a70337 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 7 Feb 2023 16:06:59 +0100 Subject: [PATCH 35/95] feat(fmc): Add 16 data bit ctor --- embassy-stm32/src/fmc/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc/mod.rs index 856a4adc..a781e437 100644 --- a/embassy-stm32/src/fmc/mod.rs +++ b/embassy-stm32/src/fmc/mod.rs @@ -107,6 +107,24 @@ impl<'d, T: Instance> Fmc<'d, T> { ] )); + fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank2: ( + bank: stm32_fmc::SdramTargetBank::Bank2, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin) + ], + ctrl: [ + (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank2: ( bank: stm32_fmc::SdramTargetBank::Bank2, addr: [ From 102b2e52cb0b98a533ec12a1c0b93d8dc9e4f826 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 7 Feb 2023 18:15:26 +0100 Subject: [PATCH 36/95] net: use released smoltcp 0.9.0 --- embassy-net/Cargo.toml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 15cbb595..4ec340b7 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -33,6 +33,12 @@ medium-ip = ["smoltcp/medium-ip"] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } +smoltcp = { version = "0.9.0", default-features = false, features = [ + "proto-ipv4", + "socket", + "async", +]} + embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-time = { version = "0.1.0", path = "../embassy-time" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } @@ -47,14 +53,3 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw atomic-pool = "1.0" embedded-nal-async = { version = "0.3.0", optional = true } atomic-polyfill = { version = "1.0" } - -[dependencies.smoltcp] -version = "0.8.0" -git = "https://github.com/smoltcp-rs/smoltcp" -rev = "5740b765749b95c18aace5de8dc21cab75ba33d4" -default-features = false -features = [ - "proto-ipv4", - "socket", - "async", -] From f5ff3c4ac31c79cedf077f559dbd5685886399cc Mon Sep 17 00:00:00 2001 From: Matt Ickstadt Date: Thu, 12 Jan 2023 14:59:25 -0600 Subject: [PATCH 37/95] usb: add support for MS OS Descriptors --- embassy-usb/Cargo.toml | 1 + embassy-usb/src/builder.rs | 15 + embassy-usb/src/lib.rs | 17 + embassy-usb/src/msos.rs | 746 +++++++++++++++++++++++++++++++++++++ 4 files changed, 779 insertions(+) create mode 100644 embassy-usb/src/msos.rs diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 1e567bb9..31d1f4ca 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -24,6 +24,7 @@ embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver- defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } heapless = "0.7.10" +widestring = { version = "1.0.2", default-features = false } # for HID usbd-hid = { version = "0.6.0", optional = true } diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 41b24fec..2c42fd64 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -130,6 +130,7 @@ pub struct Builder<'d, D: Driver<'d>> { device_descriptor: DescriptorWriter<'d>, config_descriptor: DescriptorWriter<'d>, bos_descriptor: BosWriter<'d>, + msos_descriptor: Option>, } impl<'d, D: Driver<'d>> Builder<'d, D> { @@ -182,6 +183,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor, config_descriptor, bos_descriptor, + msos_descriptor: None, } } @@ -199,6 +201,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { self.bos_descriptor.writer.into_buf(), self.interfaces, self.control_buf, + self.msos_descriptor, ) } @@ -234,6 +237,18 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { iface_count_index, } } + + /// Add an MS OS 2.0 Descriptor Set. + /// + /// Panics if called more than once. + pub fn msos_descriptor(&mut self, msos_descriptor: crate::msos::MsOsDescriptorSet<'d>) { + if self.msos_descriptor.is_some() { + panic!("msos_descriptor already set"); + } + self.msos_descriptor + .insert(msos_descriptor) + .write_bos_capability(&mut self.bos_descriptor); + } } /// Function builder. diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 2656af29..948b8d52 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -13,6 +13,7 @@ pub mod class; pub mod control; pub mod descriptor; mod descriptor_reader; +pub mod msos; pub mod types; use embassy_futures::select::{select, Either}; @@ -135,6 +136,8 @@ struct Inner<'d, D: Driver<'d>> { set_address_pending: bool, interfaces: Vec, MAX_INTERFACE_COUNT>, + + msos_descriptor: Option>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { @@ -147,6 +150,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { bos_descriptor: &'d [u8], interfaces: Vec, MAX_INTERFACE_COUNT>, control_buf: &'d mut [u8], + msos_descriptor: Option>, ) -> UsbDevice<'d, D> { // Start the USB bus. // This prevent further allocation by consuming the driver. @@ -170,6 +174,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { address: 0, set_address_pending: false, interfaces, + msos_descriptor, }, } } @@ -603,6 +608,18 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { None => InResponse::Rejected, } } + (RequestType::Vendor, Recipient::Device) => { + if let Some(msos) = &self.msos_descriptor { + if req.request == msos.vendor_code() && req.index == 7 { + // Index 7 retrieves the MS OS Descriptor Set + InResponse::Accepted(msos.descriptor()) + } else { + InResponse::Rejected + } + } else { + InResponse::Rejected + } + } _ => InResponse::Rejected, } } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs new file mode 100644 index 00000000..08a5074b --- /dev/null +++ b/embassy-usb/src/msos.rs @@ -0,0 +1,746 @@ +//! Microsoft OS Descriptors +//! +//! + +#![allow(dead_code)] + +use core::mem::size_of; +use core::ops::Range; + +pub use widestring::{u16cstr, U16CStr}; + +use crate::descriptor::{capability_type, BosWriter}; +use crate::types::InterfaceNumber; + +fn write_u16>(buf: &mut [u8], range: Range, data: T) { + (&mut buf[range]).copy_from_slice(data.into().to_le_bytes().as_slice()) +} + +/// A serialized Microsoft OS 2.0 Descriptor set. +/// +/// Create with [`DeviceDescriptorSetBuilder`]. +pub struct MsOsDescriptorSet<'a> { + descriptor: &'a [u8], + windows_version: u32, + vendor_code: u8, +} + +impl<'a> MsOsDescriptorSet<'a> { + pub fn descriptor(&self) -> &[u8] { + self.descriptor + } + + pub fn vendor_code(&self) -> u8 { + self.vendor_code + } + + pub fn write_bos_capability(&self, bos: &mut BosWriter) { + let windows_version = self.windows_version.to_le_bytes(); + let len = self.descriptor.len().to_le_bytes(); + bos.capability( + capability_type::PLATFORM, + &[ + 0, // reserved + // platform capability UUID, Microsoft OS 2.0 platform compabitility + 0xdf, + 0x60, + 0xdd, + 0xd8, + 0x89, + 0x45, + 0xc7, + 0x4c, + 0x9c, + 0xd2, + 0x65, + 0x9d, + 0x9e, + 0x64, + 0x8a, + 0x9f, + // Minimum compatible Windows version + windows_version[0], + windows_version[1], + windows_version[2], + windows_version[3], + // Descriptor set length + len[0], + len[1], + self.vendor_code, + 0x0, // Device does not support alternate enumeration + ], + ) + } +} + +/// A helper struct to implement the different descriptor set builders. +struct DescriptorSetBuilder<'a> { + used: usize, + buf: &'a mut [u8], +} + +impl<'a> DescriptorSetBuilder<'a> { + pub fn descriptor(&mut self, desc: T) + where + T: Descriptor + 'a, + { + let size = desc.size(); + let start = self.used; + let end = start + size; + desc.write_to(&mut self.buf[start..end]); + self.used += size; + } + + pub fn subset(&mut self, build_subset: impl FnOnce(&mut DescriptorSetBuilder<'_>)) { + self.used += { + let mut subset = DescriptorSetBuilder { + used: 0, + buf: self.remaining(), + }; + build_subset(&mut subset); + subset.used + }; + } + + pub fn remaining(&mut self) -> &mut [u8] { + &mut self.buf[self.used..] + } +} + +pub mod windows_version { + pub const WIN2K: u32 = 0x05000000; + pub const WIN2KSP1: u32 = 0x05000100; + pub const WIN2KSP2: u32 = 0x05000200; + pub const WIN2KSP3: u32 = 0x05000300; + pub const WIN2KSP4: u32 = 0x05000400; + + pub const WINXP: u32 = 0x05010000; + pub const WINXPSP1: u32 = 0x05010100; + pub const WINXPSP2: u32 = 0x05010200; + pub const WINXPSP3: u32 = 0x05010300; + pub const WINXPSP4: u32 = 0x05010400; + + pub const VISTA: u32 = 0x06000000; + pub const VISTASP1: u32 = 0x06000100; + pub const VISTASP2: u32 = 0x06000200; + pub const VISTASP3: u32 = 0x06000300; + pub const VISTASP4: u32 = 0x06000400; + + pub const WIN7: u32 = 0x06010000; + pub const WIN8: u32 = 0x06020000; + /// AKA `NTDDI_WINBLUE` + pub const WIN8_1: u32 = 0x06030000; + pub const WIN10: u32 = 0x0A000000; +} + +/// Helps build a Microsoft OS 2.0 Descriptor set. +/// +/// # Example +/// ```rust +/// # use embassy_usb::types::InterfaceNumber; +/// # use embassy_usb::msos::*; +/// # let cdc_interface = unsafe { core::mem::transmute::(0) }; +/// # let dfu_interface = unsafe { core::mem::transmute::(1) }; +/// let mut buf = [0u8; 256]; +/// let mut builder = DeviceDescriptorSetBuilder::new(&mut buf[..], windows_version::WIN8_1); +/// builder.feature(MinimumRecoveryTimeDescriptor::new(5, 10)); +/// builder.feature(ModelIdDescriptor::new(0xdeadbeef1234u128)); +/// builder.configuration(1, |conf| { +/// conf.function(cdc_interface, |func| { +/// func.winusb_device(); +/// func.feature(VendorRevisionDescriptor::new(1)); +/// }); +/// conf.function(dfu_interface, |func| { +/// func.winusb_device(); +/// func.feature(VendorRevisionDescriptor::new(1)); +/// }); +/// }); +/// ``` +pub struct DeviceDescriptorSetBuilder<'a> { + builder: DescriptorSetBuilder<'a>, + windows_version: u32, + vendor_code: u8, +} + +impl<'a> DeviceDescriptorSetBuilder<'a> { + /// Create a device descriptor set builder. + /// + /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] + /// module. + /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. + pub fn new<'b: 'a>(buf: &'b mut [u8], windows_version: u32, vendor_code: u8) -> Self { + let mut builder = DescriptorSetBuilder { used: 0, buf }; + builder.descriptor(DescriptorSetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (DescriptorSetHeader::TYPE as u16).to_le(), + dwWindowsVersion: windows_version.to_le(), + wTotalLength: 0, + }); + Self { + builder, + windows_version, + vendor_code, + } + } + + /// Add a device-level feature descriptor. + /// + /// Note that some feature descriptors may only be used at the device level in non-composite devices. + pub fn feature(&mut self, desc: T) + where + T: Descriptor + DeviceLevelDescriptor + 'a, + { + self.builder.descriptor(desc) + } + + /// Add a configuration subset. + pub fn configuration(&mut self, configuration: u8, build_conf: impl FnOnce(&mut ConfigurationSubsetBuilder<'_>)) { + let mut cb = ConfigurationSubsetBuilder::new(self.builder.remaining(), configuration); + build_conf(&mut cb); + self.builder.used += cb.finalize(); + } + + /// Finishes writing the data. + pub fn finalize(self) -> MsOsDescriptorSet<'a> { + let used = self.builder.used; + let buf = self.builder.buf; + // Update length in header with final length + let total_len = &mut buf[4..6]; + total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); + + MsOsDescriptorSet { + descriptor: &buf[..used], + windows_version: self.windows_version, + vendor_code: self.vendor_code, + } + } +} + +pub struct ConfigurationSubsetBuilder<'a> { + builder: DescriptorSetBuilder<'a>, +} + +impl<'a> ConfigurationSubsetBuilder<'a> { + pub fn new<'b: 'a>(buf: &'b mut [u8], configuration: u8) -> Self { + let mut builder = DescriptorSetBuilder { used: 0, buf }; + builder.descriptor(ConfigurationSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (ConfigurationSubsetHeader::TYPE as u16).to_le(), + bConfigurationValue: configuration, + bReserved: 0, + wTotalLength: 0, + }); + Self { builder } + } + + /// Add a function subset. + pub fn function(&mut self, interface: InterfaceNumber, build_func: impl FnOnce(&mut FunctionSubsetBuilder<'_>)) { + let mut fb = FunctionSubsetBuilder::new(self.builder.remaining(), interface); + build_func(&mut fb); + self.builder.used += fb.finalize(); + } + + /// Finishes writing the data. Returns the total number of bytes used by the descriptor set. + pub fn finalize(self) -> usize { + let used = self.builder.used; + let buf = self.builder.buf; + // Update length in header with final length + let total_len = &mut buf[6..8]; + total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); + used + } +} + +pub struct FunctionSubsetBuilder<'a> { + builder: DescriptorSetBuilder<'a>, +} + +impl<'a> FunctionSubsetBuilder<'a> { + pub fn new<'b: 'a>(buf: &'b mut [u8], interface: InterfaceNumber) -> Self { + let mut builder = DescriptorSetBuilder { used: 0, buf }; + builder.descriptor(FunctionSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (FunctionSubsetHeader::TYPE as u16).to_le(), + bFirstInterface: interface.0, + bReserved: 0, + wSubsetLength: 0, + }); + Self { builder } + } + + /// Add a function-level descriptor. + /// + /// Note that many descriptors can only be used at function-level in a composite device. + pub fn feature(&mut self, desc: T) + where + T: Descriptor + FunctionLevelDescriptor + 'a, + { + self.builder.descriptor(desc) + } + + /// Adds the feature descriptors to configure this function to use the WinUSB driver. + /// + /// Adds a compatible id descriptor "WINUSB" and a registry descriptor that sets the DeviceInterfaceGUID to the + /// USB_DEVICE GUID. + pub fn winusb_device(&mut self) { + self.feature(CompatibleIdFeatureDescriptor::new_winusb()); + self.feature(RegistryPropertyFeatureDescriptor::new_usb_deviceinterfaceguid()); + } + + /// Finishes writing the data. Returns the total number of bytes used by the descriptor set. + pub fn finalize(self) -> usize { + let used = self.builder.used; + let buf = self.builder.buf; + // Update length in header with final length + let total_len = &mut buf[6..8]; + total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); + used + } +} + +/// A trait for descriptors +pub trait Descriptor: Sized { + const TYPE: DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() + } + + fn write_to(&self, buf: &mut [u8]); +} + +/// Copies the data of `t` into `buf`. +/// +/// # Safety +/// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) +unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { + let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); + assert!(buf.len() >= bytes.len()); + (&mut buf[..bytes.len()]).copy_from_slice(bytes); +} + +/// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DescriptorType { + SetHeaderDescriptor = 0, + SubsetHeaderConfiguration = 1, + SubsetHeaderFunction = 2, + FeatureCompatibleId = 3, + FeatureRegProperty = 4, + FeatureMinResumeTime = 5, + FeatureModelId = 6, + FeatureCcgpDevice = 7, + FeatureVendorRevision = 8, +} + +/// Table 5. Descriptor set information structure. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetInformation { + dwWindowsVersion: u32, + wMSOSDescriptorSetTotalLength: u16, + bMS_VendorCode: u8, + bAltEnumCode: u8, +} + +/// Table 4. Microsoft OS 2.0 platform capability descriptor header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct PlatformDescriptor { + bLength: u8, + bDescriptorType: u8, + bDevCapabilityType: u8, + bReserved: u8, + platformCapabilityUUID: [u8; 16], + descriptor_set_information: DescriptorSetInformation, +} + +/// Table 10. Microsoft OS 2.0 descriptor set header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetHeader { + wLength: u16, + wDescriptorType: u16, + dwWindowsVersion: u32, + wTotalLength: u16, +} + +impl Descriptor for DescriptorSetHeader { + const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +/// Table 11. Configuration subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ConfigurationSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bConfigurationValue: u8, + bReserved: u8, + wTotalLength: u16, +} + +impl Descriptor for ConfigurationSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +/// Table 12. Function subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct FunctionSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bFirstInterface: u8, + bReserved: u8, + wSubsetLength: u16, +} + +impl Descriptor for FunctionSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +// Feature Descriptors + +/// A marker trait for feature descriptors that are valid at the device level. +pub trait DeviceLevelDescriptor {} + +/// A marker trait for feature descriptors that are valid at the function level. +pub trait FunctionLevelDescriptor { + /// `true` when the feature descriptor may only be used at the function level in composite devices. + const COMPOSITE_ONLY: bool = false; +} + +/// Table 13. Microsoft OS 2.0 compatible ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CompatibleIdFeatureDescriptor { + wLength: u16, + wDescriptorType: u16, + compatibleId: [u8; 8], + subCompatibleId: [u8; 8], +} + +impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} +impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor { + const COMPOSITE_ONLY: bool = true; +} + +impl Descriptor for CompatibleIdFeatureDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CompatibleIdFeatureDescriptor { + /// Creates a compatible ID descriptor that signals WINUSB driver compatiblilty. + pub fn new_winusb() -> Self { + Self::new_raw([b'W', b'I', b'N', b'U', b'S', b'B', 0, 0], [0u8; 8]) + } + + /// The ids must be 8 ASCII bytes or fewer. + pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { + assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); + let mut cid = [0u8; 8]; + (&mut cid[..compatible_id.len()]).copy_from_slice(compatible_id.as_bytes()); + let mut scid = [0u8; 8]; + (&mut scid[..sub_compatible_id.len()]).copy_from_slice(sub_compatible_id.as_bytes()); + Self::new_raw(cid, scid) + } + + pub fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + compatibleId: compatible_id, + subCompatibleId: sub_compatible_id, + } + } +} + +/// Table 14. Microsoft OS 2.0 registry property descriptor +#[allow(non_snake_case)] +pub struct RegistryPropertyFeatureDescriptor<'a> { + wLength: u16, + wDescriptorType: u16, + wPropertyDataType: u16, + wPropertyNameLength: u16, + PropertyName: &'a [u8], + wPropertyDataLength: u16, + PropertyData: &'a [u8], +} + +impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> { + const COMPOSITE_ONLY: bool = true; +} + +impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { + const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; + fn size(&self) -> usize { + 10 + self.PropertyName.len() + self.PropertyData.len() + } + fn write_to(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.size()); + assert!(self.wPropertyNameLength as usize == self.PropertyName.len()); + assert!(self.wPropertyDataLength as usize == self.PropertyData.len()); + write_u16(buf, 0..2, self.wLength); + write_u16(buf, 2..4, self.wDescriptorType); + write_u16(buf, 4..6, self.wPropertyDataType); + write_u16(buf, 6..8, self.wPropertyNameLength); + let pne = 8 + self.PropertyName.len(); + (&mut buf[8..pne]).copy_from_slice(self.PropertyName); + let pds = pne + 2; + let pde = pds + self.PropertyData.len(); + write_u16(buf, pne..pds, self.wPropertyDataLength); + (&mut buf[pds..pde]).copy_from_slice(self.PropertyData); + } +} + +impl<'a> RegistryPropertyFeatureDescriptor<'a> { + /// A registry property. + /// + /// `name` should be a NUL-terminated 16-bit Unicode string. + pub fn new_raw<'n: 'a, 'd: 'a>(name: &'a [u8], data: &'d [u8], data_type: PropertyDataType) -> Self { + Self { + wLength: ((10 + name.len() + data.len()) as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + wPropertyDataType: (data_type as u16).to_le(), + wPropertyNameLength: (name.len() as u16).to_le(), + PropertyName: name, + wPropertyDataLength: (data.len() as u16).to_le(), + PropertyData: data, + } + } + + fn u16str_bytes(s: &U16CStr) -> &[u8] { + unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, (s.len() + 1) * 2) } + } + + /// A registry property that sets the DeviceInterfaceGUID to the device interface class for USB devices which are + /// attached to a USB hub. + pub fn new_usb_deviceinterfaceguid() -> Self { + // Can't use defmt::panic in constant expressions (inside u16cstr!) + macro_rules! panic { + ($($x:tt)*) => { + { + ::core::panic!($($x)*); + } + }; + } + + Self::new_string( + u16cstr!("DeviceInterfaceGUID"), + u16cstr!("{A5DCBF10-6530-11D2-901F-00C04FB951ED}"), + ) + } + + /// A registry property containing a NUL-terminated 16-bit Unicode string. + pub fn new_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { + Self::new_raw(Self::u16str_bytes(name), Self::u16str_bytes(data), PropertyDataType::Sz) + } + + /// A registry property containing a NUL-terminated 16-bit Unicode string that expands environment variables. + pub fn new_string_expand<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { + Self::new_raw( + Self::u16str_bytes(name), + Self::u16str_bytes(data), + PropertyDataType::ExpandSz, + ) + } + + /// A registry property containing a NUL-terminated 16-bit Unicode string that contains a symbolic link. + pub fn new_link<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { + Self::new_raw( + Self::u16str_bytes(name), + Self::u16str_bytes(data), + PropertyDataType::Link, + ) + } + + /// A registry property containing multiple NUL-terminated 16-bit Unicode strings. + pub fn new_multi_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u16]) -> Self { + Self::new_raw( + Self::u16str_bytes(name), + unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) }, + PropertyDataType::RegMultiSz, + ) + } + + /// A registry property containing binary data. + pub fn new_binary<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u8]) -> Self { + Self::new_raw(Self::u16str_bytes(name), data, PropertyDataType::Binary) + } + + /// A registry property containing a Little-Endian 32-bit integer. + /// + /// The function assumes that `data` is already little-endian, it does not convert it. + pub fn new_dword_le<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self { + Self::new_raw( + Self::u16str_bytes(name), + unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::()) }, + PropertyDataType::DwordLittleEndian, + ) + } + + /// A registry property containing a big-endian 32-bit integer. + /// + /// The function assumes that `data` is already big-endian, it does not convert it. + pub fn new_dword_be<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self { + Self::new_raw( + Self::u16str_bytes(name), + unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::()) }, + PropertyDataType::DwordBigEndian, + ) + } +} + +/// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum PropertyDataType { + Sz = 1, + ExpandSz = 2, + Binary = 3, + DwordLittleEndian = 4, + DwordBigEndian = 5, + Link = 6, + RegMultiSz = 7, +} + +/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct MinimumRecoveryTimeDescriptor { + wLength: u16, + wDescriptorType: u16, + bResumeRecoveryTime: u8, + bResumeSignalingTime: u8, +} + +impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {} + +impl Descriptor for MinimumRecoveryTimeDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl MinimumRecoveryTimeDescriptor { + /// Times are in milliseconds. + /// + /// `resume_recovery_time` must be >= 0 and <= 10. + /// `resume_signaling_time` must be >= 1 and <= 20. + pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self { + assert!(resume_recovery_time <= 10); + assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bResumeRecoveryTime: resume_recovery_time, + bResumeSignalingTime: resume_signaling_time, + } + } +} + +/// Table 17. Microsoft OS 2.0 model ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ModelIdDescriptor { + wLength: u16, + wDescriptorType: u16, + modelId: [u8; 16], +} + +impl DeviceLevelDescriptor for ModelIdDescriptor {} + +impl Descriptor for ModelIdDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureModelId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl ModelIdDescriptor { + pub fn new(model_id: u128) -> Self { + Self::new_bytes(model_id.to_le_bytes()) + } + + pub fn new_bytes(model_id: [u8; 16]) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + modelId: model_id, + } + } +} + +/// Table 18. Microsoft OS 2.0 CCGP device descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CcgpDeviceDescriptor { + wLength: u16, + wDescriptorType: u16, +} + +impl DeviceLevelDescriptor for CcgpDeviceDescriptor {} + +impl Descriptor for CcgpDeviceDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CcgpDeviceDescriptor { + pub fn new() -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + } + } +} + +/// Table 19. Microsoft OS 2.0 vendor revision descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct VendorRevisionDescriptor { + wLength: u16, + wDescriptorType: u16, + /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or + /// other MSOS descriptor. Shell set to greater than or equal to 1. + VendorRevision: u16, +} + +impl DeviceLevelDescriptor for VendorRevisionDescriptor {} +impl FunctionLevelDescriptor for VendorRevisionDescriptor {} + +impl Descriptor for VendorRevisionDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl VendorRevisionDescriptor { + pub fn new(revision: u16) -> Self { + assert!(revision >= 1); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + VendorRevision: revision.to_le(), + } + } +} From 617b0a03b94d8efde3e89bd80b67caf9a0d4a35d Mon Sep 17 00:00:00 2001 From: Matt Ickstadt Date: Fri, 13 Jan 2023 10:18:33 -0600 Subject: [PATCH 38/95] usb: fix descriptor set length and DeviceInterfaceGUIDs --- embassy-usb/src/msos.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 08a5074b..8a2889ce 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -205,7 +205,7 @@ impl<'a> DeviceDescriptorSetBuilder<'a> { let used = self.builder.used; let buf = self.builder.buf; // Update length in header with final length - let total_len = &mut buf[4..6]; + let total_len = &mut buf[8..10]; total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); MsOsDescriptorSet { @@ -528,7 +528,7 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, (s.len() + 1) * 2) } } - /// A registry property that sets the DeviceInterfaceGUID to the device interface class for USB devices which are + /// A registry property that sets the DeviceInterfaceGUIDs to the device interface class for USB devices which are /// attached to a USB hub. pub fn new_usb_deviceinterfaceguid() -> Self { // Can't use defmt::panic in constant expressions (inside u16cstr!) @@ -540,9 +540,9 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { }; } - Self::new_string( - u16cstr!("DeviceInterfaceGUID"), - u16cstr!("{A5DCBF10-6530-11D2-901F-00C04FB951ED}"), + Self::new_multi_string( + u16cstr!("DeviceInterfaceGUIDs"), + u16cstr!("{A5DCBF10-6530-11D2-901F-00C04FB951ED}").as_slice_with_nul(), ) } From b9ecdb72bb55792a8fa5a0bace8cdad498fee9b0 Mon Sep 17 00:00:00 2001 From: Matt Ickstadt Date: Fri, 13 Jan 2023 12:21:29 -0600 Subject: [PATCH 39/95] usb: remove msos dead code --- embassy-usb/src/msos.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 8a2889ce..df846125 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -2,8 +2,6 @@ //! //! -#![allow(dead_code)] - use core::mem::size_of; use core::ops::Range; @@ -91,17 +89,6 @@ impl<'a> DescriptorSetBuilder<'a> { self.used += size; } - pub fn subset(&mut self, build_subset: impl FnOnce(&mut DescriptorSetBuilder<'_>)) { - self.used += { - let mut subset = DescriptorSetBuilder { - used: 0, - buf: self.remaining(), - }; - build_subset(&mut subset); - subset.used - }; - } - pub fn remaining(&mut self) -> &mut [u8] { &mut self.buf[self.used..] } From 9f9230ae7abb545822e59c6f06cabb721b63e0a1 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Thu, 2 Feb 2023 16:13:16 -0500 Subject: [PATCH 40/95] Convert MS OS descriptor builder to a writer API This brings it inline with the other embassy-usb descriptor APIs and allows it to integrate well with the Builder to allow class constructors to add MS OS descriptors. Also adds a `usb_serial_winusb` example to demonstrate how to use the API. --- embassy-usb/Cargo.toml | 3 +- embassy-usb/src/builder.rs | 78 ++- embassy-usb/src/lib.rs | 14 +- embassy-usb/src/msos.rs | 493 +++++++++--------- examples/nrf52840/Cargo.toml | 7 +- .../nrf52840/src/bin/usb_serial_winusb.rs | 139 +++++ 6 files changed, 461 insertions(+), 273 deletions(-) create mode 100644 examples/nrf52840/src/bin/usb_serial_winusb.rs diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 31d1f4ca..54a8f27c 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -13,6 +13,7 @@ target = "thumbv7em-none-eabi" [features] defmt = ["dep:defmt", "embassy-usb-driver/defmt"] usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] +msos-descriptor = ["dep:widestring"] default = ["usbd-hid"] [dependencies] @@ -24,7 +25,7 @@ embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver- defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } heapless = "0.7.10" -widestring = { version = "1.0.2", default-features = false } +widestring = { version = "1.0.2", default-features = false, optional = true } # for HID usbd-hid = { version = "0.6.0", optional = true } diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 2c42fd64..d1cbf674 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -3,6 +3,8 @@ use heapless::Vec; use crate::control::ControlHandler; use crate::descriptor::{BosWriter, DescriptorWriter}; use crate::driver::{Driver, Endpoint, EndpointType}; +#[cfg(feature = "msos-descriptor")] +use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::*; use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; @@ -130,7 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> { device_descriptor: DescriptorWriter<'d>, config_descriptor: DescriptorWriter<'d>, bos_descriptor: BosWriter<'d>, - msos_descriptor: Option>, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter<'d>, } impl<'d, D: Driver<'d>> Builder<'d, D> { @@ -145,6 +149,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8], + #[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8], control_buf: &'d mut [u8], handler: Option<&'d dyn DeviceStateHandler>, ) -> Self { @@ -183,12 +188,17 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor, config_descriptor, bos_descriptor, - msos_descriptor: None, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf), } } /// Creates the [`UsbDevice`] instance with the configuration in this builder. pub fn build(mut self) -> UsbDevice<'d, D> { + #[cfg(feature = "msos-descriptor")] + let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); + self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); @@ -201,7 +211,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { self.bos_descriptor.writer.into_buf(), self.interfaces, self.control_buf, - self.msos_descriptor, + #[cfg(feature = "msos-descriptor")] + msos_descriptor, ) } @@ -218,14 +229,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { /// /// If it's not set, no IAD descriptor is added. pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { + let first_interface = InterfaceNumber::new(self.interfaces.len() as u8); let iface_count_index = if self.config.composite_with_iads { - self.config_descriptor.iad( - InterfaceNumber::new(self.interfaces.len() as _), - 0, - class, - subclass, - protocol, - ); + self.config_descriptor + .iad(first_interface, 0, class, subclass, protocol); Some(self.config_descriptor.position() - 5) } else { @@ -235,19 +242,31 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { FunctionBuilder { builder: self, iface_count_index, + + #[cfg(feature = "msos-descriptor")] + first_interface, } } + #[cfg(feature = "msos-descriptor")] /// Add an MS OS 2.0 Descriptor Set. /// /// Panics if called more than once. - pub fn msos_descriptor(&mut self, msos_descriptor: crate::msos::MsOsDescriptorSet<'d>) { - if self.msos_descriptor.is_some() { - panic!("msos_descriptor already set"); - } - self.msos_descriptor - .insert(msos_descriptor) - .write_bos_capability(&mut self.bos_descriptor); + pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) { + self.msos_descriptor.header(windows_version, vendor_code); + } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Device Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + self.msos_descriptor.device_feature(desc); + } + + #[cfg(feature = "msos-descriptor")] + /// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that + /// do not add their own. + pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> { + &mut self.msos_descriptor } } @@ -259,6 +278,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { builder: &'a mut Builder<'d, D>, iface_count_index: Option, + + #[cfg(feature = "msos-descriptor")] + first_interface: InterfaceNumber, +} + +impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { + fn drop(&mut self) { + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.end_function(); + } } impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { @@ -288,6 +317,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { next_alt_setting_number: 0, } } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Function Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + if !self.builder.msos_descriptor.is_in_config_subset() { + self.builder.msos_descriptor.configuration(0); + } + + if !self.builder.msos_descriptor.is_in_function_subset() { + self.builder.msos_descriptor.function(self.first_interface.0); + } + + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.function_feature(desc); + } } /// Interface builder. diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 948b8d52..aec18524 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -136,8 +136,8 @@ struct Inner<'d, D: Driver<'d>> { set_address_pending: bool, interfaces: Vec, MAX_INTERFACE_COUNT>, - - msos_descriptor: Option>, + #[cfg(feature = "msos-descriptor")] + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { @@ -150,7 +150,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { bos_descriptor: &'d [u8], interfaces: Vec, MAX_INTERFACE_COUNT>, control_buf: &'d mut [u8], - msos_descriptor: Option>, + #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, ) -> UsbDevice<'d, D> { // Start the USB bus. // This prevent further allocation by consuming the driver. @@ -174,6 +174,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { address: 0, set_address_pending: false, interfaces, + #[cfg(feature = "msos-descriptor")] msos_descriptor, }, } @@ -608,11 +609,12 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { None => InResponse::Rejected, } } + #[cfg(feature = "msos-descriptor")] (RequestType::Vendor, Recipient::Device) => { - if let Some(msos) = &self.msos_descriptor { - if req.request == msos.vendor_code() && req.index == 7 { + if !self.msos_descriptor.is_empty() { + if req.request == self.msos_descriptor.vendor_code() && req.index == 7 { // Index 7 retrieves the MS OS Descriptor Set - InResponse::Accepted(msos.descriptor()) + InResponse::Accepted(self.msos_descriptor.descriptor()) } else { InResponse::Rejected } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index df846125..360f80d9 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "msos-descriptor")] + //! Microsoft OS Descriptors //! //! @@ -5,10 +7,9 @@ use core::mem::size_of; use core::ops::Range; -pub use widestring::{u16cstr, U16CStr}; +pub use widestring::{u16cstr, u16str, U16CStr, U16Str}; -use crate::descriptor::{capability_type, BosWriter}; -use crate::types::InterfaceNumber; +use super::{capability_type, BosWriter}; fn write_u16>(buf: &mut [u8], range: Range, data: T) { (&mut buf[range]).copy_from_slice(data.into().to_le_bytes().as_slice()) @@ -17,13 +18,12 @@ fn write_u16>(buf: &mut [u8], range: Range, data: T) { /// A serialized Microsoft OS 2.0 Descriptor set. /// /// Create with [`DeviceDescriptorSetBuilder`]. -pub struct MsOsDescriptorSet<'a> { - descriptor: &'a [u8], - windows_version: u32, +pub struct MsOsDescriptorSet<'d> { + descriptor: &'d [u8], vendor_code: u8, } -impl<'a> MsOsDescriptorSet<'a> { +impl<'d> MsOsDescriptorSet<'d> { pub fn descriptor(&self) -> &[u8] { self.descriptor } @@ -32,9 +32,150 @@ impl<'a> MsOsDescriptorSet<'a> { self.vendor_code } - pub fn write_bos_capability(&self, bos: &mut BosWriter) { - let windows_version = self.windows_version.to_le_bytes(); - let len = self.descriptor.len().to_le_bytes(); + pub fn is_empty(&self) -> bool { + self.descriptor.is_empty() + } +} + +/// Writes a Microsoft OS 2.0 Descriptor set into a buffer. +pub struct MsOsDescriptorWriter<'d> { + pub buf: &'d mut [u8], + + position: usize, + config_mark: Option, + function_mark: Option, + vendor_code: u8, +} + +impl<'d> MsOsDescriptorWriter<'d> { + pub(crate) fn new(buf: &'d mut [u8]) -> Self { + MsOsDescriptorWriter { + buf, + position: 0, + config_mark: None, + function_mark: None, + vendor_code: 0, + } + } + + pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> { + self.end(); + + if self.is_empty() { + MsOsDescriptorSet { + descriptor: &[], + vendor_code: 0, + } + } else { + self.write_bos(bos); + MsOsDescriptorSet { + descriptor: &self.buf[..self.position], + vendor_code: self.vendor_code, + } + } + } + + pub fn is_empty(&self) -> bool { + self.position == 0 + } + + pub fn is_in_config_subset(&self) -> bool { + self.config_mark.is_some() + } + + pub fn is_in_function_subset(&self) -> bool { + self.function_mark.is_some() + } + + /// Write the MS OS descriptor set header. + /// + /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] + /// module. + /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. + pub fn header(&mut self, windows_version: u32, vendor_code: u8) { + assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once"); + self.write(DescriptorSetHeader::new(windows_version)); + self.vendor_code = vendor_code; + } + + /// Add a device level feature descriptor. + /// + /// Note that some feature descriptors may only be used at the device level in non-composite devices. + /// Those features must be written before the first call to [`Self::configuration`]. + pub fn device_feature(&mut self, desc: T) { + assert!( + !self.is_empty(), + "device features may only be added after the header is written" + ); + assert!( + self.config_mark.is_none(), + "device features must be added before the first configuration subset" + ); + self.write(desc); + } + + /// Add a configuration subset. + pub fn configuration(&mut self, config: u8) { + assert!( + !self.is_empty(), + "MsOsDescriptorWriter: configuration must be called after header" + ); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + self.config_mark = Some(self.position); + self.write(ConfigurationSubsetHeader::new(config)); + } + + /// Add a function subset. + pub fn function(&mut self, first_interface: u8) { + assert!( + self.config_mark.is_some(), + "MsOsDescriptorWriter: function subset requires a configuration subset" + ); + self.end_function(); + self.function_mark = Some(self.position); + self.write(FunctionSubsetHeader::new(first_interface)); + } + + /// Add a function level feature descriptor. + /// + /// Note that some features may only be used at the function level. Those features must be written after a call + /// to [`Self::function`]. + pub fn function_feature(&mut self, desc: T) { + assert!( + self.function_mark.is_some(), + "function features may only be added to a function subset" + ); + self.write(desc); + } + + pub fn end_function(&mut self) { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + } + + fn write(&mut self, desc: T) { + desc.write_to(&mut self.buf[self.position..]); + self.position += desc.size(); + } + + fn end_subset(buf: &mut [u8], position: usize, mark: &mut Option) { + if let Some(mark) = mark.take() { + let len = position - mark; + let p = mark + T::LENGTH_OFFSET; + buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes()); + } + } + + fn end(&mut self) { + if self.position > 0 { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + Self::end_subset::(self.buf, self.position, &mut Some(0)); + } + } + + fn write_bos(&mut self, bos: &mut BosWriter) { + let windows_version = &self.buf[4..8]; + let len = (self.position as u16).to_le_bytes(); bos.capability( capability_type::PLATFORM, &[ @@ -67,30 +208,7 @@ impl<'a> MsOsDescriptorSet<'a> { self.vendor_code, 0x0, // Device does not support alternate enumeration ], - ) - } -} - -/// A helper struct to implement the different descriptor set builders. -struct DescriptorSetBuilder<'a> { - used: usize, - buf: &'a mut [u8], -} - -impl<'a> DescriptorSetBuilder<'a> { - pub fn descriptor(&mut self, desc: T) - where - T: Descriptor + 'a, - { - let size = desc.size(); - let start = self.used; - let end = start + size; - desc.write_to(&mut self.buf[start..end]); - self.used += size; - } - - pub fn remaining(&mut self) -> &mut [u8] { - &mut self.buf[self.used..] + ); } } @@ -120,182 +238,27 @@ pub mod windows_version { pub const WIN10: u32 = 0x0A000000; } -/// Helps build a Microsoft OS 2.0 Descriptor set. -/// -/// # Example -/// ```rust -/// # use embassy_usb::types::InterfaceNumber; -/// # use embassy_usb::msos::*; -/// # let cdc_interface = unsafe { core::mem::transmute::(0) }; -/// # let dfu_interface = unsafe { core::mem::transmute::(1) }; -/// let mut buf = [0u8; 256]; -/// let mut builder = DeviceDescriptorSetBuilder::new(&mut buf[..], windows_version::WIN8_1); -/// builder.feature(MinimumRecoveryTimeDescriptor::new(5, 10)); -/// builder.feature(ModelIdDescriptor::new(0xdeadbeef1234u128)); -/// builder.configuration(1, |conf| { -/// conf.function(cdc_interface, |func| { -/// func.winusb_device(); -/// func.feature(VendorRevisionDescriptor::new(1)); -/// }); -/// conf.function(dfu_interface, |func| { -/// func.winusb_device(); -/// func.feature(VendorRevisionDescriptor::new(1)); -/// }); -/// }); -/// ``` -pub struct DeviceDescriptorSetBuilder<'a> { - builder: DescriptorSetBuilder<'a>, - windows_version: u32, - vendor_code: u8, -} +mod sealed { + use core::mem::size_of; -impl<'a> DeviceDescriptorSetBuilder<'a> { - /// Create a device descriptor set builder. - /// - /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] - /// module. - /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. - pub fn new<'b: 'a>(buf: &'b mut [u8], windows_version: u32, vendor_code: u8) -> Self { - let mut builder = DescriptorSetBuilder { used: 0, buf }; - builder.descriptor(DescriptorSetHeader { - wLength: (size_of::() as u16).to_le(), - wDescriptorType: (DescriptorSetHeader::TYPE as u16).to_le(), - dwWindowsVersion: windows_version.to_le(), - wTotalLength: 0, - }); - Self { - builder, - windows_version, - vendor_code, + /// A trait for descriptors + pub trait Descriptor: Sized { + const TYPE: super::DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() } + + fn write_to(&self, buf: &mut [u8]); } - /// Add a device-level feature descriptor. - /// - /// Note that some feature descriptors may only be used at the device level in non-composite devices. - pub fn feature(&mut self, desc: T) - where - T: Descriptor + DeviceLevelDescriptor + 'a, - { - self.builder.descriptor(desc) - } - - /// Add a configuration subset. - pub fn configuration(&mut self, configuration: u8, build_conf: impl FnOnce(&mut ConfigurationSubsetBuilder<'_>)) { - let mut cb = ConfigurationSubsetBuilder::new(self.builder.remaining(), configuration); - build_conf(&mut cb); - self.builder.used += cb.finalize(); - } - - /// Finishes writing the data. - pub fn finalize(self) -> MsOsDescriptorSet<'a> { - let used = self.builder.used; - let buf = self.builder.buf; - // Update length in header with final length - let total_len = &mut buf[8..10]; - total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); - - MsOsDescriptorSet { - descriptor: &buf[..used], - windows_version: self.windows_version, - vendor_code: self.vendor_code, - } + pub trait DescriptorSet: Descriptor { + const LENGTH_OFFSET: usize; } } -pub struct ConfigurationSubsetBuilder<'a> { - builder: DescriptorSetBuilder<'a>, -} - -impl<'a> ConfigurationSubsetBuilder<'a> { - pub fn new<'b: 'a>(buf: &'b mut [u8], configuration: u8) -> Self { - let mut builder = DescriptorSetBuilder { used: 0, buf }; - builder.descriptor(ConfigurationSubsetHeader { - wLength: (size_of::() as u16).to_le(), - wDescriptorType: (ConfigurationSubsetHeader::TYPE as u16).to_le(), - bConfigurationValue: configuration, - bReserved: 0, - wTotalLength: 0, - }); - Self { builder } - } - - /// Add a function subset. - pub fn function(&mut self, interface: InterfaceNumber, build_func: impl FnOnce(&mut FunctionSubsetBuilder<'_>)) { - let mut fb = FunctionSubsetBuilder::new(self.builder.remaining(), interface); - build_func(&mut fb); - self.builder.used += fb.finalize(); - } - - /// Finishes writing the data. Returns the total number of bytes used by the descriptor set. - pub fn finalize(self) -> usize { - let used = self.builder.used; - let buf = self.builder.buf; - // Update length in header with final length - let total_len = &mut buf[6..8]; - total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); - used - } -} - -pub struct FunctionSubsetBuilder<'a> { - builder: DescriptorSetBuilder<'a>, -} - -impl<'a> FunctionSubsetBuilder<'a> { - pub fn new<'b: 'a>(buf: &'b mut [u8], interface: InterfaceNumber) -> Self { - let mut builder = DescriptorSetBuilder { used: 0, buf }; - builder.descriptor(FunctionSubsetHeader { - wLength: (size_of::() as u16).to_le(), - wDescriptorType: (FunctionSubsetHeader::TYPE as u16).to_le(), - bFirstInterface: interface.0, - bReserved: 0, - wSubsetLength: 0, - }); - Self { builder } - } - - /// Add a function-level descriptor. - /// - /// Note that many descriptors can only be used at function-level in a composite device. - pub fn feature(&mut self, desc: T) - where - T: Descriptor + FunctionLevelDescriptor + 'a, - { - self.builder.descriptor(desc) - } - - /// Adds the feature descriptors to configure this function to use the WinUSB driver. - /// - /// Adds a compatible id descriptor "WINUSB" and a registry descriptor that sets the DeviceInterfaceGUID to the - /// USB_DEVICE GUID. - pub fn winusb_device(&mut self) { - self.feature(CompatibleIdFeatureDescriptor::new_winusb()); - self.feature(RegistryPropertyFeatureDescriptor::new_usb_deviceinterfaceguid()); - } - - /// Finishes writing the data. Returns the total number of bytes used by the descriptor set. - pub fn finalize(self) -> usize { - let used = self.builder.used; - let buf = self.builder.buf; - // Update length in header with final length - let total_len = &mut buf[6..8]; - total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); - used - } -} - -/// A trait for descriptors -pub trait Descriptor: Sized { - const TYPE: DescriptorType; - - /// The size of the descriptor's header. - fn size(&self) -> usize { - size_of::() - } - - fn write_to(&self, buf: &mut [u8]); -} +use sealed::*; /// Copies the data of `t` into `buf`. /// @@ -303,7 +266,7 @@ pub trait Descriptor: Sized { /// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); - assert!(buf.len() >= bytes.len()); + assert!(buf.len() >= bytes.len(), "MSOS descriptor buffer full"); (&mut buf[..bytes.len()]).copy_from_slice(bytes); } @@ -354,6 +317,17 @@ pub struct DescriptorSetHeader { wTotalLength: u16, } +impl DescriptorSetHeader { + pub fn new(windows_version: u32) -> Self { + DescriptorSetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + dwWindowsVersion: windows_version.to_le(), + wTotalLength: 0, + } + } +} + impl Descriptor for DescriptorSetHeader { const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; fn write_to(&self, buf: &mut [u8]) { @@ -361,6 +335,10 @@ impl Descriptor for DescriptorSetHeader { } } +impl DescriptorSet for DescriptorSetHeader { + const LENGTH_OFFSET: usize = 8; +} + /// Table 11. Configuration subset header. #[allow(non_snake_case)] #[repr(C, packed(1))] @@ -372,6 +350,18 @@ pub struct ConfigurationSubsetHeader { wTotalLength: u16, } +impl ConfigurationSubsetHeader { + pub fn new(config: u8) -> Self { + ConfigurationSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bConfigurationValue: config, + bReserved: 0, + wTotalLength: 0, + } + } +} + impl Descriptor for ConfigurationSubsetHeader { const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; fn write_to(&self, buf: &mut [u8]) { @@ -379,6 +369,10 @@ impl Descriptor for ConfigurationSubsetHeader { } } +impl DescriptorSet for ConfigurationSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + /// Table 12. Function subset header. #[allow(non_snake_case)] #[repr(C, packed(1))] @@ -390,6 +384,18 @@ pub struct FunctionSubsetHeader { wSubsetLength: u16, } +impl FunctionSubsetHeader { + pub fn new(first_interface: u8) -> Self { + FunctionSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bFirstInterface: first_interface, + bReserved: 0, + wSubsetLength: 0, + } + } +} + impl Descriptor for FunctionSubsetHeader { const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; fn write_to(&self, buf: &mut [u8]) { @@ -397,16 +403,17 @@ impl Descriptor for FunctionSubsetHeader { } } +impl DescriptorSet for FunctionSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + // Feature Descriptors /// A marker trait for feature descriptors that are valid at the device level. -pub trait DeviceLevelDescriptor {} +pub trait DeviceLevelDescriptor: Descriptor {} /// A marker trait for feature descriptors that are valid at the function level. -pub trait FunctionLevelDescriptor { - /// `true` when the feature descriptor may only be used at the function level in composite devices. - const COMPOSITE_ONLY: bool = false; -} +pub trait FunctionLevelDescriptor: Descriptor {} /// Table 13. Microsoft OS 2.0 compatible ID descriptor. #[allow(non_snake_case)] @@ -419,9 +426,7 @@ pub struct CompatibleIdFeatureDescriptor { } impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} -impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor { - const COMPOSITE_ONLY: bool = true; -} +impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {} impl Descriptor for CompatibleIdFeatureDescriptor { const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; @@ -462,16 +467,12 @@ pub struct RegistryPropertyFeatureDescriptor<'a> { wLength: u16, wDescriptorType: u16, wPropertyDataType: u16, - wPropertyNameLength: u16, PropertyName: &'a [u8], - wPropertyDataLength: u16, PropertyData: &'a [u8], } impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} -impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> { - const COMPOSITE_ONLY: bool = true; -} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; @@ -479,45 +480,22 @@ impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { 10 + self.PropertyName.len() + self.PropertyData.len() } fn write_to(&self, buf: &mut [u8]) { - assert!(buf.len() >= self.size()); - assert!(self.wPropertyNameLength as usize == self.PropertyName.len()); - assert!(self.wPropertyDataLength as usize == self.PropertyData.len()); + assert!(buf.len() >= self.size(), "MSOS descriptor buffer full"); write_u16(buf, 0..2, self.wLength); write_u16(buf, 2..4, self.wDescriptorType); write_u16(buf, 4..6, self.wPropertyDataType); - write_u16(buf, 6..8, self.wPropertyNameLength); + write_u16(buf, 6..8, (self.PropertyName.len() as u16).to_le()); let pne = 8 + self.PropertyName.len(); (&mut buf[8..pne]).copy_from_slice(self.PropertyName); let pds = pne + 2; let pde = pds + self.PropertyData.len(); - write_u16(buf, pne..pds, self.wPropertyDataLength); + write_u16(buf, pne..pds, (self.PropertyData.len() as u16).to_le()); (&mut buf[pds..pde]).copy_from_slice(self.PropertyData); } } impl<'a> RegistryPropertyFeatureDescriptor<'a> { - /// A registry property. - /// - /// `name` should be a NUL-terminated 16-bit Unicode string. - pub fn new_raw<'n: 'a, 'd: 'a>(name: &'a [u8], data: &'d [u8], data_type: PropertyDataType) -> Self { - Self { - wLength: ((10 + name.len() + data.len()) as u16).to_le(), - wDescriptorType: (Self::TYPE as u16).to_le(), - wPropertyDataType: (data_type as u16).to_le(), - wPropertyNameLength: (name.len() as u16).to_le(), - PropertyName: name, - wPropertyDataLength: (data.len() as u16).to_le(), - PropertyData: data, - } - } - - fn u16str_bytes(s: &U16CStr) -> &[u8] { - unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, (s.len() + 1) * 2) } - } - - /// A registry property that sets the DeviceInterfaceGUIDs to the device interface class for USB devices which are - /// attached to a USB hub. - pub fn new_usb_deviceinterfaceguid() -> Self { + pub const DEVICE_INTERFACE_GUIDS_NAME: &U16CStr = { // Can't use defmt::panic in constant expressions (inside u16cstr!) macro_rules! panic { ($($x:tt)*) => { @@ -527,10 +505,25 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { }; } - Self::new_multi_string( - u16cstr!("DeviceInterfaceGUIDs"), - u16cstr!("{A5DCBF10-6530-11D2-901F-00C04FB951ED}").as_slice_with_nul(), - ) + u16cstr!("DeviceInterfaceGUIDs") + }; + + /// A registry property. + /// + /// `name` should be a NUL-terminated 16-bit Unicode string. + pub fn new_raw<'n: 'a, 'd: 'a>(name: &'a [u8], data: &'d [u8], data_type: PropertyDataType) -> Self { + assert!(name.len() < usize::from(u16::MAX) && data.len() < usize::from(u16::MAX)); + Self { + wLength: ((10 + name.len() + data.len()) as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + wPropertyDataType: (data_type as u16).to_le(), + PropertyName: name, + PropertyData: data, + } + } + + fn u16str_bytes(s: &U16CStr) -> &[u8] { + unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, (s.len() + 1) * 2) } } /// A registry property containing a NUL-terminated 16-bit Unicode string. @@ -558,6 +551,10 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { /// A registry property containing multiple NUL-terminated 16-bit Unicode strings. pub fn new_multi_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u16]) -> Self { + assert!( + data.len() >= 2 && data[data.len() - 1] == 0 && data[data.len() - 2] == 0, + "multi-strings must end in double nul terminators" + ); Self::new_raw( Self::u16str_bytes(name), unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) }, diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 95d93987..cfdda076 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0" [features] default = ["nightly"] +msos-descriptor = ["embassy-usb/msos-descriptor"] nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", "embassy-lora", "lorawan-device", "lorawan"] @@ -34,4 +35,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" usbd-hid = "0.6.0" -serde = { version = "1.0.136", default-features = false } \ No newline at end of file +serde = { version = "1.0.136", default-features = false } + +[[bin]] +name = "usb_serial_winusb" +required-features = ["msos-descriptor"] diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs new file mode 100644 index 00000000..443379a0 --- /dev/null +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -0,0 +1,139 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; +use embassy_nrf::{interrupt, pac}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +const DEVICE_INTERFACE_GUIDS: &[u16] = { + // Can't use defmt::panic in constant expressions (inside u16str!) + macro_rules! panic { + ($($x:tt)*) => { + { + ::core::panic!($($x)*); + } + }; + } + msos::u16str!("{EAA9A5DC-30BA-44BC-9232-606CDC875321}\0\0").as_slice() +}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let power_irq = interrupt::take!(POWER_CLOCK); + let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatiblity. + // 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 device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + None, + ); + + builder.msos_descriptor(windows_version::WIN8_1, 2); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Since we want to create MS OS feature descriptors that apply to a function that has already been added to the + // builder, need to get the MsOsDescriptorWriter from the builder and manually add those descriptors. + // Inside a class constructor, you would just need to call `FunctionBuilder::msos_feature` instead. + let msos_writer = builder.msos_writer(); + msos_writer.configuration(0); + msos_writer.function(0); + msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new_multi_string( + msos::RegistryPropertyFeatureDescriptor::DEVICE_INTERFACE_GUIDS_NAME, + DEVICE_INTERFACE_GUIDS, + )); + + // 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, P: UsbSupply + 'd>( + class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, +) -> 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 aa21aebb0b321a2085571e5be5fffcea4703584d Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 7 Feb 2023 14:19:51 -0500 Subject: [PATCH 41/95] Lazily encode UTF16 values and add docs --- embassy-usb/Cargo.toml | 3 +- embassy-usb/src/msos.rs | 318 +++++++++--------- .../nrf52840/src/bin/usb_serial_winusb.rs | 25 +- 3 files changed, 166 insertions(+), 180 deletions(-) diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 54a8f27c..eb9ba36f 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -13,7 +13,7 @@ target = "thumbv7em-none-eabi" [features] defmt = ["dep:defmt", "embassy-usb-driver/defmt"] usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] -msos-descriptor = ["dep:widestring"] +msos-descriptor = [] default = ["usbd-hid"] [dependencies] @@ -25,7 +25,6 @@ embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver- defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } heapless = "0.7.10" -widestring = { version = "1.0.2", default-features = false, optional = true } # for HID usbd-hid = { version = "0.6.0", optional = true } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 360f80d9..19ed3497 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -5,16 +5,9 @@ //! use core::mem::size_of; -use core::ops::Range; - -pub use widestring::{u16cstr, u16str, U16CStr, U16Str}; use super::{capability_type, BosWriter}; -fn write_u16>(buf: &mut [u8], range: Range, data: T) { - (&mut buf[range]).copy_from_slice(data.into().to_le_bytes().as_slice()) -} - /// A serialized Microsoft OS 2.0 Descriptor set. /// /// Create with [`DeviceDescriptorSetBuilder`]. @@ -24,14 +17,17 @@ pub struct MsOsDescriptorSet<'d> { } impl<'d> MsOsDescriptorSet<'d> { + /// Gets the raw bytes of the MS OS descriptor pub fn descriptor(&self) -> &[u8] { self.descriptor } + /// Gets the vendor code used by the host to retrieve the MS OS descriptor pub fn vendor_code(&self) -> u8 { self.vendor_code } + /// Returns `true` if no MS OS descriptor data is available pub fn is_empty(&self) -> bool { self.descriptor.is_empty() } @@ -39,7 +35,7 @@ impl<'d> MsOsDescriptorSet<'d> { /// Writes a Microsoft OS 2.0 Descriptor set into a buffer. pub struct MsOsDescriptorWriter<'d> { - pub buf: &'d mut [u8], + buf: &'d mut [u8], position: usize, config_mark: Option, @@ -75,14 +71,17 @@ impl<'d> MsOsDescriptorWriter<'d> { } } + /// Returns `true` if the MS OS descriptor header has not yet been written pub fn is_empty(&self) -> bool { self.position == 0 } + /// Returns `true` if a configuration subset header has been started pub fn is_in_config_subset(&self) -> bool { self.config_mark.is_some() } + /// Returns `true` if a function subset header has been started and not yet ended pub fn is_in_function_subset(&self) -> bool { self.function_mark.is_some() } @@ -148,6 +147,7 @@ impl<'d> MsOsDescriptorWriter<'d> { self.write(desc); } + /// Ends the current function subset (if any) pub fn end_function(&mut self) { Self::end_subset::(self.buf, self.position, &mut self.function_mark); } @@ -212,29 +212,13 @@ impl<'d> MsOsDescriptorWriter<'d> { } } +/// Microsoft Windows version codes +/// +/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors. pub mod windows_version { - pub const WIN2K: u32 = 0x05000000; - pub const WIN2KSP1: u32 = 0x05000100; - pub const WIN2KSP2: u32 = 0x05000200; - pub const WIN2KSP3: u32 = 0x05000300; - pub const WIN2KSP4: u32 = 0x05000400; - - pub const WINXP: u32 = 0x05010000; - pub const WINXPSP1: u32 = 0x05010100; - pub const WINXPSP2: u32 = 0x05010200; - pub const WINXPSP3: u32 = 0x05010300; - pub const WINXPSP4: u32 = 0x05010400; - - pub const VISTA: u32 = 0x06000000; - pub const VISTASP1: u32 = 0x06000100; - pub const VISTASP2: u32 = 0x06000200; - pub const VISTASP3: u32 = 0x06000300; - pub const VISTASP4: u32 = 0x06000400; - - pub const WIN7: u32 = 0x06010000; - pub const WIN8: u32 = 0x06020000; - /// AKA `NTDDI_WINBLUE` + /// Windows 8.1 (aka `NTDDI_WINBLUE`) pub const WIN8_1: u32 = 0x06030000; + /// Windows 10 pub const WIN10: u32 = 0x0A000000; } @@ -266,7 +250,7 @@ use sealed::*; /// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); - assert!(buf.len() >= bytes.len(), "MSOS descriptor buffer full"); + assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full"); (&mut buf[..bytes.len()]).copy_from_slice(bytes); } @@ -274,14 +258,23 @@ unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum DescriptorType { + /// MS OS descriptor set header SetHeaderDescriptor = 0, + /// Configuration subset header SubsetHeaderConfiguration = 1, + /// Function subset header SubsetHeaderFunction = 2, + /// Compatible device ID feature descriptor FeatureCompatibleId = 3, + /// Registry property feature descriptor FeatureRegProperty = 4, + /// Minimum USB resume time feature descriptor FeatureMinResumeTime = 5, + /// Vendor revision feature descriptor FeatureModelId = 6, + /// CCGP device descriptor feature descriptor FeatureCcgpDevice = 7, + /// Vendor revision feature descriptor FeatureVendorRevision = 8, } @@ -318,6 +311,9 @@ pub struct DescriptorSetHeader { } impl DescriptorSetHeader { + /// Creates a MS OS descriptor set header. + /// + /// `windows_version` is the minimum Windows version the descriptor set can apply to. pub fn new(windows_version: u32) -> Self { DescriptorSetHeader { wLength: (size_of::() as u16).to_le(), @@ -351,6 +347,7 @@ pub struct ConfigurationSubsetHeader { } impl ConfigurationSubsetHeader { + /// Creates a configuration subset header pub fn new(config: u8) -> Self { ConfigurationSubsetHeader { wLength: (size_of::() as u16).to_le(), @@ -385,6 +382,7 @@ pub struct FunctionSubsetHeader { } impl FunctionSubsetHeader { + /// Creates a function subset header pub fn new(first_interface: u8) -> Self { FunctionSubsetHeader { wLength: (size_of::() as u16).to_le(), @@ -436,11 +434,8 @@ impl Descriptor for CompatibleIdFeatureDescriptor { } impl CompatibleIdFeatureDescriptor { - /// Creates a compatible ID descriptor that signals WINUSB driver compatiblilty. - pub fn new_winusb() -> Self { - Self::new_raw([b'W', b'I', b'N', b'U', b'S', b'B', 0, 0], [0u8; 8]) - } - + /// Creates a compatible ID feature descriptor + /// /// The ids must be 8 ASCII bytes or fewer. pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); @@ -451,7 +446,7 @@ impl CompatibleIdFeatureDescriptor { Self::new_raw(cid, scid) } - pub fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { + fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { Self { wLength: (size_of::() as u16).to_le(), wDescriptorType: (Self::TYPE as u16).to_le(), @@ -464,129 +459,87 @@ impl CompatibleIdFeatureDescriptor { /// Table 14. Microsoft OS 2.0 registry property descriptor #[allow(non_snake_case)] pub struct RegistryPropertyFeatureDescriptor<'a> { - wLength: u16, - wDescriptorType: u16, - wPropertyDataType: u16, - PropertyName: &'a [u8], - PropertyData: &'a [u8], + name: &'a str, + data: PropertyData<'a>, } -impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} -impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} - -impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { - const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; - fn size(&self) -> usize { - 10 + self.PropertyName.len() + self.PropertyData.len() - } - fn write_to(&self, buf: &mut [u8]) { - assert!(buf.len() >= self.size(), "MSOS descriptor buffer full"); - write_u16(buf, 0..2, self.wLength); - write_u16(buf, 2..4, self.wDescriptorType); - write_u16(buf, 4..6, self.wPropertyDataType); - write_u16(buf, 6..8, (self.PropertyName.len() as u16).to_le()); - let pne = 8 + self.PropertyName.len(); - (&mut buf[8..pne]).copy_from_slice(self.PropertyName); - let pds = pne + 2; - let pde = pds + self.PropertyData.len(); - write_u16(buf, pne..pds, (self.PropertyData.len() as u16).to_le()); - (&mut buf[pds..pde]).copy_from_slice(self.PropertyData); - } -} - -impl<'a> RegistryPropertyFeatureDescriptor<'a> { - pub const DEVICE_INTERFACE_GUIDS_NAME: &U16CStr = { - // Can't use defmt::panic in constant expressions (inside u16cstr!) - macro_rules! panic { - ($($x:tt)*) => { - { - ::core::panic!($($x)*); - } - }; - } - - u16cstr!("DeviceInterfaceGUIDs") - }; - - /// A registry property. - /// - /// `name` should be a NUL-terminated 16-bit Unicode string. - pub fn new_raw<'n: 'a, 'd: 'a>(name: &'a [u8], data: &'d [u8], data_type: PropertyDataType) -> Self { - assert!(name.len() < usize::from(u16::MAX) && data.len() < usize::from(u16::MAX)); - Self { - wLength: ((10 + name.len() + data.len()) as u16).to_le(), - wDescriptorType: (Self::TYPE as u16).to_le(), - wPropertyDataType: (data_type as u16).to_le(), - PropertyName: name, - PropertyData: data, - } - } - - fn u16str_bytes(s: &U16CStr) -> &[u8] { - unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, (s.len() + 1) * 2) } - } - - /// A registry property containing a NUL-terminated 16-bit Unicode string. - pub fn new_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { - Self::new_raw(Self::u16str_bytes(name), Self::u16str_bytes(data), PropertyDataType::Sz) - } - - /// A registry property containing a NUL-terminated 16-bit Unicode string that expands environment variables. - pub fn new_string_expand<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - Self::u16str_bytes(data), - PropertyDataType::ExpandSz, - ) - } - - /// A registry property containing a NUL-terminated 16-bit Unicode string that contains a symbolic link. - pub fn new_link<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - Self::u16str_bytes(data), - PropertyDataType::Link, - ) - } - - /// A registry property containing multiple NUL-terminated 16-bit Unicode strings. - pub fn new_multi_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u16]) -> Self { - assert!( - data.len() >= 2 && data[data.len() - 1] == 0 && data[data.len() - 2] == 0, - "multi-strings must end in double nul terminators" - ); - Self::new_raw( - Self::u16str_bytes(name), - unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) }, - PropertyDataType::RegMultiSz, - ) - } - +/// Data values that can be encoded into a registry property descriptor +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PropertyData<'a> { + /// A registry property containing a string. + Sz(&'a str), + /// A registry property containing a string that expands environment variables. + ExpandSz(&'a str), /// A registry property containing binary data. - pub fn new_binary<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u8]) -> Self { - Self::new_raw(Self::u16str_bytes(name), data, PropertyDataType::Binary) - } - - /// A registry property containing a Little-Endian 32-bit integer. - /// - /// The function assumes that `data` is already little-endian, it does not convert it. - pub fn new_dword_le<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::()) }, - PropertyDataType::DwordLittleEndian, - ) - } - + Binary(&'a [u8]), + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian(u32), /// A registry property containing a big-endian 32-bit integer. - /// - /// The function assumes that `data` is already big-endian, it does not convert it. - pub fn new_dword_be<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::()) }, - PropertyDataType::DwordBigEndian, - ) + DwordBigEndian(u32), + /// A registry property containing a string that contains a symbolic link. + Link(&'a str), + /// A registry property containing multiple strings. + RegMultiSz(&'a [&'a str]), +} + +fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize { + assert!(buf.len() >= val.len()); + buf[..val.len()].copy_from_slice(val); + val.len() +} + +fn write_utf16(val: &str, buf: &mut [u8]) -> usize { + let mut pos = 0; + for c in val.encode_utf16() { + pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) +} + +impl<'a> PropertyData<'a> { + /// Gets the `PropertyDataType` for this property value + pub fn kind(&self) -> PropertyDataType { + match self { + PropertyData::Sz(_) => PropertyDataType::Sz, + PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz, + PropertyData::Binary(_) => PropertyDataType::Binary, + PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian, + PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian, + PropertyData::Link(_) => PropertyDataType::Link, + PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz, + } + } + + /// Gets the size (in bytes) of this property value when encoded. + pub fn size(&self) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => { + core::mem::size_of::() * (val.encode_utf16().count() + 1) + } + PropertyData::Binary(val) => val.len(), + PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), + PropertyData::RegMultiSz(val) => { + core::mem::size_of::() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1 + } + } + } + + /// Encodes the data for this property value and writes it to `buf`. + pub fn write(&self, buf: &mut [u8]) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf), + PropertyData::Binary(val) => write_bytes(val, buf), + PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf), + PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf), + PropertyData::RegMultiSz(val) => { + let mut pos = 0; + for s in *val { + pos += write_utf16(s, &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) + } + } } } @@ -594,15 +547,57 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum PropertyDataType { + /// A registry property containing a string. Sz = 1, + /// A registry property containing a string that expands environment variables. ExpandSz = 2, + /// A registry property containing binary data. Binary = 3, + /// A registry property containing a little-endian 32-bit integer. DwordLittleEndian = 4, + /// A registry property containing a big-endian 32-bit integer. DwordBigEndian = 5, + /// A registry property containing a string that contains a symbolic link. Link = 6, + /// A registry property containing multiple strings. RegMultiSz = 7, } +impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} + +impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { + const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; + + fn size(&self) -> usize { + 10 + self.name_size() + self.data.size() + } + + fn write_to(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.size(), "MS OS descriptor buffer full"); + + let mut pos = 0; + pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_utf16(self.name, &mut buf[pos..]); + pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]); + self.data.write(&mut buf[pos..]); + } +} + +impl<'a> RegistryPropertyFeatureDescriptor<'a> { + /// A registry property. + pub fn new(name: &'a str, data: PropertyData<'a>) -> Self { + Self { name, data } + } + + fn name_size(&self) -> usize { + core::mem::size_of::() * (self.name.encode_utf16().count() + 1) + } +} + /// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. #[allow(non_snake_case)] #[repr(C, packed(1))] @@ -658,15 +653,14 @@ impl Descriptor for ModelIdDescriptor { } impl ModelIdDescriptor { + /// Creates a new model ID descriptor + /// + /// `model_id` should be a uuid that uniquely identifies a physical device. pub fn new(model_id: u128) -> Self { - Self::new_bytes(model_id.to_le_bytes()) - } - - pub fn new_bytes(model_id: [u8; 16]) -> Self { Self { wLength: (size_of::() as u16).to_le(), wDescriptorType: (Self::TYPE as u16).to_le(), - modelId: model_id, + modelId: model_id.to_le_bytes(), } } } @@ -689,6 +683,7 @@ impl Descriptor for CcgpDeviceDescriptor { } impl CcgpDeviceDescriptor { + /// Creates a new CCGP device descriptor pub fn new() -> Self { Self { wLength: (size_of::() as u16).to_le(), @@ -704,7 +699,7 @@ pub struct VendorRevisionDescriptor { wLength: u16, wDescriptorType: u16, /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or - /// other MSOS descriptor. Shell set to greater than or equal to 1. + /// other MS OS descriptor. Shell set to greater than or equal to 1. VendorRevision: u16, } @@ -719,6 +714,7 @@ impl Descriptor for VendorRevisionDescriptor { } impl VendorRevisionDescriptor { + /// Creates a new vendor revision descriptor pub fn new(revision: u16) -> Self { assert!(revision >= 1); Self { diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs index 443379a0..f4b828de 100644 --- a/examples/nrf52840/src/bin/usb_serial_winusb.rs +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -7,7 +7,7 @@ use core::mem; use defmt::{info, panic}; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; @@ -15,17 +15,8 @@ use embassy_usb::msos::{self, windows_version}; use embassy_usb::{Builder, Config}; use {defmt_rtt as _, panic_probe as _}; -const DEVICE_INTERFACE_GUIDS: &[u16] = { - // Can't use defmt::panic in constant expressions (inside u16str!) - macro_rules! panic { - ($($x:tt)*) => { - { - ::core::panic!($($x)*); - } - }; - } - msos::u16str!("{EAA9A5DC-30BA-44BC-9232-606CDC875321}\0\0").as_slice() -}; +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -39,7 +30,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -89,9 +80,9 @@ async fn main(_spawner: Spawner) { msos_writer.configuration(0); msos_writer.function(0); msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); - msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new_multi_string( - msos::RegistryPropertyFeatureDescriptor::DEVICE_INTERFACE_GUIDS_NAME, - DEVICE_INTERFACE_GUIDS, + msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), )); // Build the builder. @@ -126,7 +117,7 @@ impl From for Disconnected { } } -async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>( +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, ) -> Result<(), Disconnected> { let mut buf = [0; 64]; From 1d841cc8ac74feacc4d231958ce2c46419ae3bda Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 7 Feb 2023 20:49:10 +0100 Subject: [PATCH 42/95] usb: make max interface count configurable at compile time. --- embassy-usb/Cargo.toml | 13 ++++++ embassy-usb/README.md | 24 +++++++++- embassy-usb/build.rs | 93 ++++++++++++++++++++++++++++++++++++++ embassy-usb/gen_config.py | 73 ++++++++++++++++++++++++++++++ embassy-usb/src/builder.rs | 5 +- embassy-usb/src/lib.rs | 9 ++-- 6 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 embassy-usb/build.rs create mode 100644 embassy-usb/gen_config.py diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index eb9ba36f..463e1268 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -16,6 +16,19 @@ usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] msos-descriptor = [] default = ["usbd-hid"] +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +max-interface-count-1 = [] +max-interface-count-2 = [] +max-interface-count-3 = [] +max-interface-count-4 = [] # Default +max-interface-count-5 = [] +max-interface-count-6 = [] +max-interface-count-7 = [] +max-interface-count-8 = [] + +# END AUTOGENERATED CONFIG FEATURES + [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } diff --git a/embassy-usb/README.md b/embassy-usb/README.md index 581c3290..a3d45b56 100644 --- a/embassy-usb/README.md +++ b/embassy-usb/README.md @@ -1,6 +1,28 @@ # embassy-usb -TODO crate description/ +TODO crate description + +## Configuration + +`embassy-usb` has some configuration settings that are set at compile time, affecting sizes +and counts of buffers. + +They can be set in two ways: + +- Via Cargo features: enable a feature like `-`. `name` must be in lowercase and +use dashes instead of underscores. For example. `max-interface-count-3`. Only a selection of values +is available, check `Cargo.toml` for the list. +- Via environment variables at build time: set the variable named `EMBASSY_USB_`. For example +`EMBASSY_USB_MAX_INTERFACE_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. +Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +### `MAX_INTERFACE_COUNT` + +Max amount of interfaces that can be created in one device. Default: 4. + ## Interoperability diff --git a/embassy-usb/build.rs b/embassy-usb/build.rs new file mode 100644 index 00000000..524bdc2f --- /dev/null +++ b/embassy-usb/build.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("MAX_INTERFACE_COUNT", 4), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); +} diff --git a/embassy-usb/gen_config.py b/embassy-usb/gen_config.py new file mode 100644 index 00000000..55a7fa3c --- /dev/null +++ b/embassy-usb/gen_config.py @@ -0,0 +1,73 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min, max, pow2=None): + vals = set() + val = min + while val <= max: + vals.add(val) + if pow2 == True or (isinstance(pow2, int) and val >= pow2): + val *= 2 + else: + val += 1 + vals.add(default) + + features.append( + { + "name": name, + "default": default, + "vals": sorted(list(vals)), + } + ) + + +feature("max_interface_count", default=4, min=1, max=8) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"{name}-{val} = []" + if val == f["default"]: + things += " # Default" + things += "\n" + things += "\n" + +SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" +HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" +with open("Cargo.toml", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after +with open("Cargo.toml", "w") as f: + f.write(data) + + +# ========= Update build.rs + +things = "" +for f in features: + name = f["name"].upper() + things += f' ("{name}", {f["default"]}),\n' + +SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" +HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" +with open("build.rs", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + things + " " + SEPARATOR_END + after +with open("build.rs", "w") as f: + f.write(data) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index d1cbf674..d89fc401 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -308,7 +308,10 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { }; if self.builder.interfaces.push(iface).is_err() { - panic!("max interface count reached") + panic!( + "embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}", + MAX_INTERFACE_COUNT + ) } InterfaceBuilder { diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index aec18524..f8983318 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -16,10 +16,16 @@ mod descriptor_reader; pub mod msos; pub mod types; +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + use embassy_futures::select::{select, Either}; use heapless::Vec; pub use crate::builder::{Builder, Config}; +use crate::config::*; use crate::control::*; use crate::descriptor::*; use crate::descriptor_reader::foreach_endpoint; @@ -71,9 +77,6 @@ pub const CONFIGURATION_NONE: u8 = 0; /// The bConfiguration value for the single configuration supported by this device. pub const CONFIGURATION_VALUE: u8 = 1; -/// Maximum interface count, configured at compile time. -pub const MAX_INTERFACE_COUNT: usize = 4; - const STRING_INDEX_MANUFACTURER: u8 = 1; const STRING_INDEX_PRODUCT: u8 = 2; const STRING_INDEX_SERIAL_NUMBER: u8 = 3; From 3af991ab63d14cfad6f50d28bfb944d1895d1c70 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 7 Feb 2023 22:49:14 +0100 Subject: [PATCH 43/95] usb: unify ControlHandler+DeviceStateHandler, route all control requests to all handlers. - Allows classes to handle vendor requests. - Allows classes to use a single handler for multiple interfaces. - Allows classes to access the other events (previously only `reset` was available). --- embassy-usb-logger/src/lib.rs | 1 - embassy-usb/Cargo.toml | 9 + embassy-usb/build.rs | 1 + embassy-usb/gen_config.py | 1 + embassy-usb/src/builder.rs | 45 ++-- embassy-usb/src/class/cdc_acm.rs | 50 ++-- embassy-usb/src/class/cdc_ncm/mod.rs | 83 +++--- embassy-usb/src/class/hid.rs | 172 +++++++------ embassy-usb/src/control.rs | 58 ----- embassy-usb/src/descriptor_reader.rs | 7 +- embassy-usb/src/lib.rs | 243 +++++++++++------- embassy-usb/src/types.rs | 6 +- examples/nrf52840/src/bin/usb_ethernet.rs | 1 - examples/nrf52840/src/bin/usb_hid_keyboard.rs | 25 +- examples/nrf52840/src/bin/usb_hid_mouse.rs | 1 - examples/nrf52840/src/bin/usb_serial.rs | 1 - .../nrf52840/src/bin/usb_serial_multitask.rs | 1 - .../nrf52840/src/bin/usb_serial_winusb.rs | 1 - examples/rp/src/bin/usb_ethernet.rs | 1 - examples/rp/src/bin/usb_serial.rs | 1 - examples/stm32f1/src/bin/usb_serial.rs | 1 - examples/stm32f3/src/bin/usb_serial.rs | 1 - examples/stm32f4/src/bin/usb_ethernet.rs | 1 - examples/stm32f4/src/bin/usb_serial.rs | 1 - examples/stm32f7/src/bin/usb_serial.rs | 1 - examples/stm32h7/src/bin/usb_serial.rs | 1 - examples/stm32l4/src/bin/usb_serial.rs | 1 - examples/stm32l5/src/bin/usb_ethernet.rs | 1 - examples/stm32l5/src/bin/usb_hid_mouse.rs | 1 - examples/stm32l5/src/bin/usb_serial.rs | 1 - examples/stm32u5/src/bin/usb_serial.rs | 1 - 31 files changed, 381 insertions(+), 338 deletions(-) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 6386e209..1d8dd13c 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -74,7 +74,6 @@ impl UsbLogger { &mut state.config_descriptor, &mut state.bos_descriptor, &mut state.control_buf, - None, ); // Create classes on the builder. diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 463e1268..ae3f3ac3 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -27,6 +27,15 @@ max-interface-count-6 = [] max-interface-count-7 = [] max-interface-count-8 = [] +max-handler-count-1 = [] +max-handler-count-2 = [] +max-handler-count-3 = [] +max-handler-count-4 = [] # Default +max-handler-count-5 = [] +max-handler-count-6 = [] +max-handler-count-7 = [] +max-handler-count-8 = [] + # END AUTOGENERATED CONFIG FEATURES [dependencies] diff --git a/embassy-usb/build.rs b/embassy-usb/build.rs index 524bdc2f..33d32f7d 100644 --- a/embassy-usb/build.rs +++ b/embassy-usb/build.rs @@ -7,6 +7,7 @@ static CONFIGS: &[(&str, usize)] = &[ // BEGIN AUTOGENERATED CONFIG FEATURES // Generated by gen_config.py. DO NOT EDIT. ("MAX_INTERFACE_COUNT", 4), + ("MAX_HANDLER_COUNT", 4), // END AUTOGENERATED CONFIG FEATURES ]; diff --git a/embassy-usb/gen_config.py b/embassy-usb/gen_config.py index 55a7fa3c..67ce359c 100644 --- a/embassy-usb/gen_config.py +++ b/embassy-usb/gen_config.py @@ -28,6 +28,7 @@ def feature(name, default, min, max, pow2=None): feature("max_interface_count", default=4, min=1, max=8) +feature("max_handler_count", default=4, min=1, max=8) # ========= Update Cargo.toml diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index d89fc401..07b2a177 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,12 +1,12 @@ use heapless::Vec; -use crate::control::ControlHandler; +use crate::config::*; use crate::descriptor::{BosWriter, DescriptorWriter}; use crate::driver::{Driver, Endpoint, EndpointType}; #[cfg(feature = "msos-descriptor")] use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::*; -use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; +use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -122,8 +122,8 @@ impl<'a> Config<'a> { /// [`UsbDevice`] builder. pub struct Builder<'d, D: Driver<'d>> { config: Config<'d>, - handler: Option<&'d dyn DeviceStateHandler>, - interfaces: Vec, MAX_INTERFACE_COUNT>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + interfaces: Vec, control_buf: &'d mut [u8], driver: D, @@ -151,7 +151,6 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { bos_descriptor_buf: &'d mut [u8], #[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8], control_buf: &'d mut [u8], - handler: Option<&'d dyn DeviceStateHandler>, ) -> Self { // Magic values specified in USB-IF ECN on IADs. if config.composite_with_iads @@ -179,9 +178,9 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { Builder { driver, - handler, config, interfaces: Vec::new(), + handlers: Vec::new(), control_buf, next_string_index: STRING_INDEX_CUSTOM_START, @@ -205,7 +204,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { UsbDevice::build( self.driver, self.config, - self.handler, + self.handlers, self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), @@ -248,6 +247,26 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { } } + /// Add a Handler. + /// + /// The Handler is called on some USB bus events, and to handle all control requests not already + /// handled by the USB stack. + pub fn handler(&mut self, handler: &'d mut dyn Handler) { + if self.handlers.push(handler).is_err() { + panic!( + "embassy-usb: handler list full. Increase the `max_handler_count` compile-time setting. Current value: {}", + MAX_HANDLER_COUNT + ) + } + } + + /// Allocates a new string index. + pub fn string(&mut self) -> StringIndex { + let index = self.next_string_index; + self.next_string_index += 1; + StringIndex::new(index) + } + #[cfg(feature = "msos-descriptor")] /// Add an MS OS 2.0 Descriptor Set. /// @@ -301,10 +320,8 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { let number = self.builder.interfaces.len() as _; let iface = Interface { - handler: None, current_alt_setting: 0, num_alt_settings: 0, - num_strings: 0, }; if self.builder.interfaces.push(iface).is_err() { @@ -350,17 +367,9 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { self.interface_number } - pub fn handler(&mut self, handler: &'d mut dyn ControlHandler) { - self.builder.interfaces[self.interface_number.0 as usize].handler = Some(handler); - } - /// Allocates a new string index. pub fn string(&mut self) -> StringIndex { - let index = self.builder.next_string_index; - self.builder.next_string_index += 1; - self.builder.interfaces[self.interface_number.0 as usize].num_strings += 1; - - StringIndex::new(index) + self.builder.string() } /// Add an alternate setting to the interface and write its descriptor. diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index fb9eaeca..ff82ad40 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -6,10 +6,10 @@ use core::sync::atomic::{AtomicBool, Ordering}; use embassy_sync::blocking_mutex::CriticalSectionMutex; -use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::types::*; -use crate::Builder; +use crate::{Builder, Handler}; /// This should be used as `device_class` when building the `UsbDevice`. pub const USB_CLASS_CDC: u8 = 0x02; @@ -67,6 +67,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { } struct Control<'a> { + comm_if: InterfaceNumber, shared: &'a ControlShared, } @@ -98,7 +99,7 @@ impl<'a> Control<'a> { } } -impl<'d> ControlHandler for Control<'d> { +impl<'d> Handler for Control<'d> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -106,12 +107,18 @@ impl<'d> ControlHandler for Control<'d> { shared.rts.store(false, Ordering::Relaxed); } - fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_LINE_CODING if data.len() >= 7 => { let coding = LineCoding { @@ -123,7 +130,7 @@ impl<'d> ControlHandler for Control<'d> { self.shared().line_coding.lock(|x| x.set(coding)); debug!("Set line coding to: {:?}", coding); - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_CONTROL_LINE_STATE => { let dtr = (req.value & 0x0001) != 0; @@ -134,13 +141,19 @@ impl<'d> ControlHandler for Control<'d> { shared.rts.store(rts, Ordering::Relaxed); debug!("Set dtr {}, rts {}", dtr, rts); - OutResponse::Accepted + Some(OutResponse::Accepted) } - _ => OutResponse::Rejected, + _ => Some(OutResponse::Rejected), } } - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { @@ -151,9 +164,9 @@ impl<'d> ControlHandler for Control<'d> { buf[4] = coding.stop_bits as u8; buf[5] = coding.parity_type as u8; buf[6] = coding.data_bits; - InResponse::Accepted(&buf[0..7]) + Some(InResponse::Accepted(&buf[0..7])) } - _ => InResponse::Rejected, + _ => Some(InResponse::Rejected), } } } @@ -162,17 +175,12 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { - let control = state.control.write(Control { shared: &state.shared }); - - let control_shared = &state.shared; - assert!(builder.control_buf_len() >= 7); let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); // Control interface let mut iface = func.interface(); - iface.handler(control); let comm_if = iface.interface_number(); let data_if = u8::from(comm_if) + 1; let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None); @@ -213,6 +221,16 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(func); + + let control = state.control.write(Control { + shared: &state.shared, + comm_if, + }); + builder.handler(control); + + let control_shared = &state.shared; + CdcAcmClass { _comm_ep: comm_ep, _data_if: data_if, diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs index d6c7d37e..262499cc 100644 --- a/embassy-usb/src/class/cdc_ncm/mod.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -17,10 +17,10 @@ use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; -use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; use crate::types::*; -use crate::Builder; +use crate::{Builder, Handler}; pub mod embassy_net; @@ -117,8 +117,7 @@ fn byteify(buf: &mut [u8], data: T) -> &[u8] { /// Internal state for the CDC-NCM class. pub struct State<'a> { - comm_control: MaybeUninit>, - data_control: MaybeUninit, + control: MaybeUninit>, shared: ControlShared, } @@ -126,8 +125,7 @@ impl<'a> State<'a> { /// Create a new `State`. pub fn new() -> Self { Self { - comm_control: MaybeUninit::uninit(), - data_control: MaybeUninit::uninit(), + control: MaybeUninit::uninit(), shared: Default::default(), } } @@ -144,29 +142,55 @@ impl Default for ControlShared { } } -struct CommControl<'a> { +struct Control<'a> { mac_addr_string: StringIndex, shared: &'a ControlShared, mac_addr_str: [u8; 12], + comm_if: InterfaceNumber, + data_if: InterfaceNumber, } -impl<'d> ControlHandler for CommControl<'d> { - fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { +impl<'d> Handler for Control<'d> { + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + if iface != self.data_if { + return; + } + + match alternate_setting { + ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), + ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), + _ => unreachable!(), + } + } + + fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_NTB_INPUT_SIZE => { // TODO - OutResponse::Accepted + Some(OutResponse::Accepted) } - _ => OutResponse::Rejected, + _ => Some(OutResponse::Rejected), } } - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_GET_NTB_PARAMETERS => { let res = NtbParameters { @@ -187,9 +211,9 @@ impl<'d> ControlHandler for CommControl<'d> { max_datagram_count: 1, // We only decode 1 packet per NTB }, }; - InResponse::Accepted(byteify(buf, res)) + Some(InResponse::Accepted(byteify(buf, res))) } - _ => InResponse::Rejected, + _ => Some(InResponse::Rejected), } } @@ -214,18 +238,6 @@ impl<'d> ControlHandler for CommControl<'d> { } } -struct DataControl {} - -impl ControlHandler for DataControl { - fn set_alternate_setting(&mut self, alternate_setting: u8) { - match alternate_setting { - ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), - ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), - _ => unreachable!(), - } - } -} - /// CDC-NCM class pub struct CdcNcmClass<'d, D: Driver<'d>> { _comm_if: InterfaceNumber, @@ -253,11 +265,6 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { // Control interface let mut iface = func.interface(); let mac_addr_string = iface.string(); - iface.handler(state.comm_control.write(CommControl { - mac_addr_string, - shared: &state.shared, - mac_addr_str: [0; 12], - })); let comm_if = iface.interface_number(); let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None); @@ -307,13 +314,23 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { // Data interface let mut iface = func.interface(); - iface.handler(state.data_control.write(DataControl {})); let data_if = iface.interface_number(); let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(func); + + let control = state.control.write(Control { + mac_addr_string, + shared: &state.shared, + mac_addr_str: [0; 12], + comm_if, + data_if, + }); + builder.handler(control); + CdcNcmClass { _comm_if: comm_if, comm_ep, diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs index 0283c112..974268c6 100644 --- a/embassy-usb/src/class/hid.rs +++ b/embassy-usb/src/class/hid.rs @@ -9,9 +9,10 @@ use ssmarshal::serialize; #[cfg(feature = "usbd-hid")] use usbd_hid::descriptor::AsInputReport; -use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use crate::Builder; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; const USB_CLASS_HID: u8 = 0x03; const USB_SUBCLASS_NONE: u8 = 0x00; @@ -100,17 +101,11 @@ fn build<'d, D: Driver<'d>>( config: Config<'d>, with_out_endpoint: bool, ) -> (Option, D::EndpointIn, &'d AtomicUsize) { - let control = state.control.write(Control::new( - config.report_descriptor, - config.request_handler, - &state.out_report_offset, - )); - let len = config.report_descriptor.len(); let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut iface = func.interface(); - iface.handler(control); + let if_num = iface.interface_number(); let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); // HID descriptor @@ -139,6 +134,16 @@ fn build<'d, D: Driver<'d>>( None }; + drop(func); + + let control = state.control.write(Control::new( + if_num, + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); + builder.handler(control); + (ep_out, ep_in, &state.out_report_offset) } @@ -400,6 +405,7 @@ pub trait RequestHandler { } struct Control<'d> { + if_num: InterfaceNumber, report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, @@ -408,11 +414,13 @@ struct Control<'d> { impl<'d> Control<'d> { fn new( + if_num: InterfaceNumber, report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, ) -> Self { Control { + if_num, report_descriptor, request_handler, out_report_offset, @@ -438,88 +446,100 @@ impl<'d> Control<'d> { } } -impl<'d> ControlHandler for Control<'d> { +impl<'d> Handler for Control<'d> { fn reset(&mut self) { self.out_report_offset.store(0, Ordering::Release); } - fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { - match (req.value >> 8) as u8 { - HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), - HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), - _ => InResponse::Rejected, + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.if_num.0 as u16) + { + return None; } - } - fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { trace!("HID control_out {:?} {=[u8]:x}", req, data); - if let RequestType::Class = req.request_type { - match req.request { - HID_REQ_SET_IDLE => { - if let Some(handler) = self.request_handler { - let id = req.value as u8; - let id = (id != 0).then(|| ReportId::In(id)); - let dur = u32::from(req.value >> 8); - let dur = if dur == 0 { u32::MAX } else { 4 * dur }; - handler.set_idle_ms(id, dur); - } - OutResponse::Accepted - } - HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { - (Ok(id), Some(handler)) => handler.set_report(id, data), - _ => OutResponse::Rejected, - }, - HID_REQ_SET_PROTOCOL => { - if req.value == 1 { - OutResponse::Accepted - } else { - warn!("HID Boot Protocol is unsupported."); - OutResponse::Rejected // UNSUPPORTED: Boot Protocol - } - } - _ => OutResponse::Rejected, - } - } else { - OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - trace!("HID control_in {:?}", req); match req.request { - HID_REQ_GET_REPORT => { - let size = match ReportId::try_from(req.value) { - Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), - Err(_) => None, - }; - - if let Some(size) = size { - InResponse::Accepted(&buf[0..size]) - } else { - InResponse::Rejected - } - } - HID_REQ_GET_IDLE => { + HID_REQ_SET_IDLE => { if let Some(handler) = self.request_handler { let id = req.value as u8; let id = (id != 0).then(|| ReportId::In(id)); - if let Some(dur) = handler.get_idle_ms(id) { - let dur = u8::try_from(dur / 4).unwrap_or(0); - buf[0] = dur; - InResponse::Accepted(&buf[0..1]) - } else { - InResponse::Rejected - } + let dur = u32::from(req.value >> 8); + let dur = if dur == 0 { u32::MAX } else { 4 * dur }; + handler.set_idle_ms(id, dur); + } + Some(OutResponse::Accepted) + } + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { + (Ok(id), Some(handler)) => Some(handler.set_report(id, data)), + _ => Some(OutResponse::Rejected), + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + Some(OutResponse::Accepted) } else { - InResponse::Rejected + warn!("HID Boot Protocol is unsupported."); + Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol } } - HID_REQ_GET_PROTOCOL => { - // UNSUPPORTED: Boot Protocol - buf[0] = 1; - InResponse::Accepted(&buf[0..1]) + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if req.index != self.if_num.0 as u16 { + return None; + } + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Interface) => match req.request { + Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => Some(InResponse::Accepted(self.report_descriptor)), + HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)), + _ => Some(InResponse::Rejected), + }, + + _ => Some(InResponse::Rejected), + }, + (RequestType::Class, Recipient::Interface) => { + trace!("HID control_in {:?}", req); + match req.request { + HID_REQ_GET_REPORT => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + Some(InResponse::Accepted(&buf[0..size])) + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_IDLE => { + if let Some(handler) = self.request_handler { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + if let Some(dur) = handler.get_idle_ms(id) { + let dur = u8::try_from(dur / 4).unwrap_or(0); + buf[0] = dur; + Some(InResponse::Accepted(&buf[0..1])) + } else { + Some(InResponse::Rejected) + } + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_PROTOCOL => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + Some(InResponse::Accepted(&buf[0..1])) + } + _ => Some(InResponse::Rejected), + } } - _ => InResponse::Rejected, + _ => None, } } } diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 39b499f0..ceccfd85 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -2,7 +2,6 @@ use core::mem; use crate::driver::Direction; -use crate::types::StringIndex; /// Control request type. #[repr(u8)] @@ -145,60 +144,3 @@ pub enum InResponse<'a> { /// The request was rejected. Rejected, } - -/// Handler for control requests. -/// -/// All methods are optional callbacks that will be called by -/// [`UsbDevice::run()`](crate::UsbDevice::run) -pub trait ControlHandler { - /// Called after a USB reset after the bus reset sequence is complete. - fn reset(&mut self) {} - - /// Called when a "set alternate setting" control request is done on the interface. - fn set_alternate_setting(&mut self, alternate_setting: u8) { - let _ = alternate_setting; - } - - /// Called when a control request is received with direction HostToDevice. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - /// * `data` - The data from the request. - fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { - let _ = (req, data); - OutResponse::Rejected - } - - /// Called when a control request is received with direction DeviceToHost. - /// - /// You should write the response somewhere (usually to `buf`, but you may use another buffer - /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - let _ = (req, buf); - InResponse::Rejected - } - - /// Called when a GET DESCRIPTOR control request is received on the interface. - /// - /// You should write the response somewhere (usually to `buf`, but you may use another buffer - /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - fn get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - let _ = (req, buf); - InResponse::Rejected - } - - /// Called when a GET_DESCRIPTOR STRING control request is received. - fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { - let _ = (index, lang_id); - None - } -} diff --git a/embassy-usb/src/descriptor_reader.rs b/embassy-usb/src/descriptor_reader.rs index d64bcb73..05adcce6 100644 --- a/embassy-usb/src/descriptor_reader.rs +++ b/embassy-usb/src/descriptor_reader.rs @@ -1,5 +1,6 @@ use crate::descriptor::descriptor_type; use crate::driver::EndpointAddress; +use crate::types::InterfaceNumber; #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -75,7 +76,7 @@ impl<'a, 'b> Iterator for DescriptorIter<'a, 'b> { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointInfo { pub configuration: u8, - pub interface: u8, + pub interface: InterfaceNumber, pub interface_alt: u8, pub ep_address: EndpointAddress, } @@ -83,7 +84,7 @@ pub struct EndpointInfo { pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<(), ReadError> { let mut ep = EndpointInfo { configuration: 0, - interface: 0, + interface: InterfaceNumber(0), interface_alt: 0, ep_address: EndpointAddress::from(0), }; @@ -96,7 +97,7 @@ pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result< ep.configuration = r.read_u8()?; } descriptor_type::INTERFACE => { - ep.interface = r.read_u8()?; + ep.interface = InterfaceNumber(r.read_u8()?); ep.interface_alt = r.read_u8()?; } descriptor_type::ENDPOINT => { diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index f8983318..bfeccd5f 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -82,32 +82,87 @@ const STRING_INDEX_PRODUCT: u8 = 2; const STRING_INDEX_SERIAL_NUMBER: u8 = 3; const STRING_INDEX_CUSTOM_START: u8 = 4; -/// A handler trait for changes in the device state of the [UsbDevice]. -pub trait DeviceStateHandler { +/// Handler for device events and control requests. +/// +/// All methods are optional callbacks that will be called by +/// [`UsbDevice::run()`](crate::UsbDevice::run) +pub trait Handler { /// Called when the USB device has been enabled or disabled. - fn enabled(&self, _enabled: bool) {} + fn enabled(&mut self, _enabled: bool) {} - /// Called when the host resets the device. - fn reset(&self) {} + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) {} /// Called when the host has set the address of the device to `addr`. - fn addressed(&self, _addr: u8) {} + fn addressed(&mut self, _addr: u8) {} /// Called when the host has enabled or disabled the configuration of the device. - fn configured(&self, _configured: bool) {} + fn configured(&mut self, _configured: bool) {} /// Called when the bus has entered or exited the suspend state. - fn suspended(&self, _suspended: bool) {} + fn suspended(&mut self, _suspended: bool) {} /// Called when remote wakeup feature is enabled or disabled. - fn remote_wakeup_enabled(&self, _enabled: bool) {} + fn remote_wakeup_enabled(&mut self, _enabled: bool) {} + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + let _ = iface; + let _ = alternate_setting; + } + + /// Called when a control request is received with direction HostToDevice. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `data` - The data from the request. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + let _ = (req, data); + None + } + + /// Called when a control request is received with direction DeviceToHost. + /// + /// You should write the response somewhere (usually to `buf`, but you may use another buffer + /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + let _ = (req, buf); + None + } + + /// Called when a GET_DESCRIPTOR STRING control request is received. + fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { + let _ = (index, lang_id); + None + } } -struct Interface<'d> { - handler: Option<&'d mut dyn ControlHandler>, +struct Interface { current_alt_setting: u8, num_alt_settings: u8, - num_strings: u8, } /// Main struct for the USB device stack. @@ -119,7 +174,6 @@ pub struct UsbDevice<'d, D: Driver<'d>> { struct Inner<'d, D: Driver<'d>> { bus: D::Bus, - handler: Option<&'d dyn DeviceStateHandler>, config: Config<'d>, device_descriptor: &'d [u8], @@ -138,7 +192,9 @@ struct Inner<'d, D: Driver<'d>> { /// instead of regular `accept()`. set_address_pending: bool, - interfaces: Vec, MAX_INTERFACE_COUNT>, + interfaces: Vec, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, } @@ -147,11 +203,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { pub(crate) fn build( driver: D, config: Config<'d>, - handler: Option<&'d dyn DeviceStateHandler>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], - interfaces: Vec, MAX_INTERFACE_COUNT>, + interfaces: Vec, control_buf: &'d mut [u8], #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, ) -> UsbDevice<'d, D> { @@ -165,7 +221,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { inner: Inner { bus, config, - handler, device_descriptor, config_descriptor, bos_descriptor, @@ -177,6 +232,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { address: 0, set_address_pending: false, interfaces, + handlers, #[cfg(feature = "msos-descriptor")] msos_descriptor, }, @@ -221,7 +277,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.inner.suspended = false; self.inner.remote_wakeup_enabled = false; - if let Some(h) = &self.inner.handler { + for h in &mut self.inner.handlers { h.enabled(false); } } @@ -250,7 +306,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.inner.bus.remote_wakeup().await?; self.inner.suspended = false; - if let Some(h) = &self.inner.handler { + for h in &mut self.inner.handlers { h.suspended(false); } @@ -361,29 +417,29 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.remote_wakeup_enabled = false; self.address = 0; - for iface in self.interfaces.iter_mut() { - iface.current_alt_setting = 0; - if let Some(h) = &mut iface.handler { - h.reset(); - h.set_alternate_setting(0); - } + for h in &mut self.handlers { + h.reset(); } - if let Some(h) = &self.handler { - h.reset(); + for (i, iface) in self.interfaces.iter_mut().enumerate() { + iface.current_alt_setting = 0; + + for h in &mut self.handlers { + h.set_alternate_setting(InterfaceNumber::new(i as _), 0); + } } } Event::Resume => { trace!("usb: resume"); self.suspended = false; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.suspended(false); } } Event::Suspend => { trace!("usb: suspend"); self.suspended = true; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.suspended(true); } } @@ -392,7 +448,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.bus.enable().await; self.device_state = UsbDeviceState::Default; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.enabled(true); } } @@ -401,7 +457,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.bus.disable().await; self.device_state = UsbDeviceState::Unpowered; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.enabled(false); } } @@ -416,14 +472,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = false; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.remote_wakeup_enabled(false); } OutResponse::Accepted } (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = true; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.remote_wakeup_enabled(true); } OutResponse::Accepted @@ -432,7 +488,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.address = addr as u8; self.set_address_pending = true; self.device_state = UsbDeviceState::Addressed; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.addressed(self.address); } OutResponse::Accepted @@ -443,14 +499,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // Enable all endpoints of selected alt settings. foreach_endpoint(self.config_descriptor, |ep| { - let iface = &self.interfaces[ep.interface as usize]; + let iface = &self.interfaces[ep.interface.0 as usize]; self.bus .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); }) .unwrap(); - // Notify handler. - if let Some(h) = &self.handler { + // Notify handlers. + for h in &mut self.handlers { h.configured(true); } @@ -468,8 +524,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { }) .unwrap(); - // Notify handler. - if let Some(h) = &self.handler { + // Notify handlers. + for h in &mut self.handlers { h.configured(false); } @@ -479,7 +535,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { _ => OutResponse::Rejected, }, (RequestType::Standard, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { + let iface_num = InterfaceNumber::new(req.index as _); + let iface = match self.interfaces.get_mut(iface_num.0 as usize) { Some(iface) => iface, None => return OutResponse::Rejected, }; @@ -497,7 +554,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // Enable/disable EPs of this interface as needed. foreach_endpoint(self.config_descriptor, |ep| { - if ep.interface == req.index as u8 { + if ep.interface == iface_num { self.bus .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); } @@ -506,8 +563,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // TODO check it is valid (not out of range) - if let Some(handler) = &mut iface.handler { - handler.set_alternate_setting(new_altsetting); + for h in &mut self.handlers { + h.set_alternate_setting(iface_num, new_altsetting); } OutResponse::Accepted } @@ -527,17 +584,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } _ => OutResponse::Rejected, }, - (RequestType::Class, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { - Some(iface) => iface, - None => return OutResponse::Rejected, - }; - match &mut iface.handler { - Some(handler) => handler.control_out(req, data), - None => OutResponse::Rejected, - } - } - _ => OutResponse::Rejected, + _ => self.handle_control_out_delegated(req, data), } } @@ -582,11 +629,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { buf[0] = iface.current_alt_setting; InResponse::Accepted(&buf[..1]) } - Request::GET_DESCRIPTOR => match &mut iface.handler { - Some(handler) => handler.get_descriptor(req, buf), - None => InResponse::Rejected, - }, - _ => InResponse::Rejected, + _ => self.handle_control_in_delegated(req, buf), } } (RequestType::Standard, Recipient::Endpoint) => match req.request { @@ -601,34 +644,48 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } _ => InResponse::Rejected, }, - (RequestType::Class, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { - Some(iface) => iface, - None => return InResponse::Rejected, - }; - - match &mut iface.handler { - Some(handler) => handler.control_in(req, buf), - None => InResponse::Rejected, - } - } #[cfg(feature = "msos-descriptor")] (RequestType::Vendor, Recipient::Device) => { - if !self.msos_descriptor.is_empty() { - if req.request == self.msos_descriptor.vendor_code() && req.index == 7 { - // Index 7 retrieves the MS OS Descriptor Set - InResponse::Accepted(self.msos_descriptor.descriptor()) - } else { - InResponse::Rejected - } + if !self.msos_descriptor.is_empty() + && req.request == self.msos_descriptor.vendor_code() + && req.index == 7 + { + // Index 7 retrieves the MS OS Descriptor Set + InResponse::Accepted(self.msos_descriptor.descriptor()) } else { - InResponse::Rejected + self.handle_control_in_delegated(req, buf) } } - _ => InResponse::Rejected, + _ => self.handle_control_in_delegated(req, buf), } } + fn handle_control_out_delegated(&mut self, req: Request, data: &[u8]) -> OutResponse { + for h in &mut self.handlers { + if let Some(res) = h.control_out(req, data) { + return res; + } + } + OutResponse::Rejected + } + + fn handle_control_in_delegated<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + unsafe fn extend_lifetime<'x, 'y>(r: InResponse<'x>) -> InResponse<'y> { + core::mem::transmute(r) + } + + for h in &mut self.handlers { + if let Some(res) = h.control_in(req, buf) { + // safety: the borrow checker isn't smart enough to know this pattern (returning a + // borrowed value from inside the loop) is sound. Workaround by unsafely extending lifetime. + // Also, Polonius (the WIP new borrow checker) does accept it. + + return unsafe { extend_lifetime(res) }; + } + } + InResponse::Rejected + } + fn handle_get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { let (dtype, index) = req.descriptor_type_index(); @@ -649,30 +706,16 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { STRING_INDEX_PRODUCT => self.config.product, STRING_INDEX_SERIAL_NUMBER => self.config.serial_number, _ => { - // Find out which iface owns this string index. - let mut index_left = index - STRING_INDEX_CUSTOM_START; - let mut the_iface = None; - for iface in &mut self.interfaces { - if index_left < iface.num_strings { - the_iface = Some(iface); + let mut s = None; + for handler in &mut self.handlers { + let index = StringIndex::new(index); + let lang_id = req.index; + if let Some(res) = handler.get_string(index, lang_id) { + s = Some(res); break; } - index_left -= iface.num_strings; - } - - if let Some(iface) = the_iface { - if let Some(handler) = &mut iface.handler { - let index = StringIndex::new(index); - let lang_id = req.index; - handler.get_string(index, lang_id) - } else { - warn!("String requested to an interface with no handler."); - None - } - } else { - warn!("String requested but didn't match to an interface."); - None } + s } }; diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index 1743e61f..15d19500 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -1,9 +1,9 @@ //! USB types. /// A handle for a USB interface that contains its number. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InterfaceNumber(pub(crate) u8); +pub struct InterfaceNumber(pub u8); impl InterfaceNumber { pub(crate) fn new(index: u8) -> InterfaceNumber { @@ -20,7 +20,7 @@ impl From for u8 { /// A handle for a USB string descriptor that contains its index. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct StringIndex(u8); +pub struct StringIndex(pub u8); impl StringIndex { pub(crate) fn new(index: u8) -> StringIndex { diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index 699666ce..97978089 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -82,7 +82,6 @@ async fn main(spawner: Spawner) { &mut singleton!([0; 256])[..], &mut singleton!([0; 256])[..], &mut singleton!([0; 128])[..], - None, ); // Our MAC addr. diff --git a/examples/nrf52840/src/bin/usb_hid_keyboard.rs b/examples/nrf52840/src/bin/usb_hid_keyboard.rs index 017cac19..3d8a114c 100644 --- a/examples/nrf52840/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -16,7 +16,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::signal::Signal; use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; -use embassy_usb::{Builder, Config, DeviceStateHandler}; +use embassy_usb::{Builder, Config, Handler}; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; @@ -52,7 +52,7 @@ async fn main(_spawner: Spawner) { let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; let request_handler = MyRequestHandler {}; - let device_state_handler = MyDeviceStateHandler::new(); + let mut device_handler = MyDeviceHandler::new(); let mut state = State::new(); @@ -63,9 +63,10 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - Some(&device_state_handler), ); + builder.handler(&mut device_handler); + // Create classes on the builder. let config = embassy_usb::class::hid::Config { report_descriptor: KeyboardReport::desc(), @@ -164,20 +165,20 @@ impl RequestHandler for MyRequestHandler { } } -struct MyDeviceStateHandler { +struct MyDeviceHandler { configured: AtomicBool, } -impl MyDeviceStateHandler { +impl MyDeviceHandler { fn new() -> Self { - MyDeviceStateHandler { + MyDeviceHandler { configured: AtomicBool::new(false), } } } -impl DeviceStateHandler for MyDeviceStateHandler { - fn enabled(&self, enabled: bool) { +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { self.configured.store(false, Ordering::Relaxed); SUSPENDED.store(false, Ordering::Release); if enabled { @@ -187,17 +188,17 @@ impl DeviceStateHandler for MyDeviceStateHandler { } } - fn reset(&self) { + fn reset(&mut self) { self.configured.store(false, Ordering::Relaxed); info!("Bus reset, the Vbus current limit is 100mA"); } - fn addressed(&self, addr: u8) { + fn addressed(&mut self, addr: u8) { self.configured.store(false, Ordering::Relaxed); info!("USB address set to: {}", addr); } - fn configured(&self, configured: bool) { + fn configured(&mut self, configured: bool) { self.configured.store(configured, Ordering::Relaxed); if configured { info!("Device configured, it may now draw up to the configured current limit from Vbus.") @@ -206,7 +207,7 @@ impl DeviceStateHandler for MyDeviceStateHandler { } } - fn suspended(&self, suspended: bool) { + fn suspended(&mut self, suspended: bool) { if suspended { info!("Device suspended, the Vbus current limit is 500µA (or 2.5mA for high-power devices with remote wakeup enabled)."); SUSPENDED.store(true, Ordering::Release); diff --git a/examples/nrf52840/src/bin/usb_hid_mouse.rs b/examples/nrf52840/src/bin/usb_hid_mouse.rs index a5849129..d7c9d55b 100644 --- a/examples/nrf52840/src/bin/usb_hid_mouse.rs +++ b/examples/nrf52840/src/bin/usb_hid_mouse.rs @@ -55,7 +55,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/nrf52840/src/bin/usb_serial.rs b/examples/nrf52840/src/bin/usb_serial.rs index 18b6f25b..102d7ea6 100644 --- a/examples/nrf52840/src/bin/usb_serial.rs +++ b/examples/nrf52840/src/bin/usb_serial.rs @@ -59,7 +59,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/nrf52840/src/bin/usb_serial_multitask.rs b/examples/nrf52840/src/bin/usb_serial_multitask.rs index 3532d3f8..558d4ba6 100644 --- a/examples/nrf52840/src/bin/usb_serial_multitask.rs +++ b/examples/nrf52840/src/bin/usb_serial_multitask.rs @@ -83,7 +83,6 @@ async fn main(spawner: Spawner) { &mut res.config_descriptor, &mut res.bos_descriptor, &mut res.control_buf, - None, ); // Create classes on the builder. diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs index f4b828de..ade6af52 100644 --- a/examples/nrf52840/src/bin/usb_serial_winusb.rs +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -65,7 +65,6 @@ async fn main(_spawner: Spawner) { &mut bos_descriptor, &mut msos_descriptor, &mut control_buf, - None, ); builder.msos_descriptor(windows_version::WIN8_1, 2); diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 104b25d3..66a6ed4d 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -73,7 +73,6 @@ async fn main(spawner: Spawner) { &mut singleton!([0; 256])[..], &mut singleton!([0; 256])[..], &mut singleton!([0; 128])[..], - None, ); // Our MAC addr. diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs index b7d6493b..a991082e 100644 --- a/examples/rp/src/bin/usb_serial.rs +++ b/examples/rp/src/bin/usb_serial.rs @@ -53,7 +53,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs index ad92cdeb..07cad84e 100644 --- a/examples/stm32f1/src/bin/usb_serial.rs +++ b/examples/stm32f1/src/bin/usb_serial.rs @@ -58,7 +58,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs index f6d27c86..5b4e0a91 100644 --- a/examples/stm32f3/src/bin/usb_serial.rs +++ b/examples/stm32f3/src/bin/usb_serial.rs @@ -55,7 +55,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index cf2885ae..4a16aac0 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -82,7 +82,6 @@ async fn main(spawner: Spawner) { &mut singleton!([0; 256])[..], &mut singleton!([0; 256])[..], &mut singleton!([0; 128])[..], - None, ); // Our MAC addr. diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs index 01464776..baabc1a2 100644 --- a/examples/stm32f4/src/bin/usb_serial.rs +++ b/examples/stm32f4/src/bin/usb_serial.rs @@ -57,7 +57,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs index 688bd0da..5fd9d2ec 100644 --- a/examples/stm32f7/src/bin/usb_serial.rs +++ b/examples/stm32f7/src/bin/usb_serial.rs @@ -58,7 +58,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs index b319d12c..9ef520ae 100644 --- a/examples/stm32h7/src/bin/usb_serial.rs +++ b/examples/stm32h7/src/bin/usb_serial.rs @@ -57,7 +57,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs index 3e38b10a..663f60d5 100644 --- a/examples/stm32l4/src/bin/usb_serial.rs +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -59,7 +59,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index e5a46b06..98ec0e83 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -79,7 +79,6 @@ async fn main(spawner: Spawner) { &mut singleton!([0; 256])[..], &mut singleton!([0; 256])[..], &mut singleton!([0; 128])[..], - None, ); // Our MAC addr. diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs index d38ed749..e3bbe9d0 100644 --- a/examples/stm32l5/src/bin/usb_hid_mouse.rs +++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -51,7 +51,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs index 7562a4e9..66ccacb7 100644 --- a/examples/stm32l5/src/bin/usb_serial.rs +++ b/examples/stm32l5/src/bin/usb_serial.rs @@ -46,7 +46,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index c846836b..8cd3bf2f 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -59,7 +59,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. From 86487db5d1773d2a764ab340051d70cfa40e4714 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 7 Feb 2023 23:45:01 +0100 Subject: [PATCH 44/95] usb: use InterfaceNumber in msos. --- embassy-usb/src/builder.rs | 2 +- embassy-usb/src/msos.rs | 7 ++++--- embassy-usb/src/types.rs | 2 ++ examples/nrf52840/src/bin/usb_serial_winusb.rs | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 07b2a177..305dfa02 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -346,7 +346,7 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { } if !self.builder.msos_descriptor.is_in_function_subset() { - self.builder.msos_descriptor.function(self.first_interface.0); + self.builder.msos_descriptor.function(self.first_interface); } #[cfg(feature = "msos-descriptor")] diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 19ed3497..b1e0335e 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -7,6 +7,7 @@ use core::mem::size_of; use super::{capability_type, BosWriter}; +use crate::types::InterfaceNumber; /// A serialized Microsoft OS 2.0 Descriptor set. /// @@ -125,7 +126,7 @@ impl<'d> MsOsDescriptorWriter<'d> { } /// Add a function subset. - pub fn function(&mut self, first_interface: u8) { + pub fn function(&mut self, first_interface: InterfaceNumber) { assert!( self.config_mark.is_some(), "MsOsDescriptorWriter: function subset requires a configuration subset" @@ -376,14 +377,14 @@ impl DescriptorSet for ConfigurationSubsetHeader { pub struct FunctionSubsetHeader { wLength: u16, wDescriptorType: u16, - bFirstInterface: u8, + bFirstInterface: InterfaceNumber, bReserved: u8, wSubsetLength: u16, } impl FunctionSubsetHeader { /// Creates a function subset header - pub fn new(first_interface: u8) -> Self { + pub fn new(first_interface: InterfaceNumber) -> Self { FunctionSubsetHeader { wLength: (size_of::() as u16).to_le(), wDescriptorType: (Self::TYPE as u16).to_le(), diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index 15d19500..c7a47f7e 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -3,6 +3,7 @@ /// A handle for a USB interface that contains its number. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] pub struct InterfaceNumber(pub u8); impl InterfaceNumber { @@ -20,6 +21,7 @@ impl From for u8 { /// A handle for a USB string descriptor that contains its index. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] pub struct StringIndex(pub u8); impl StringIndex { diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs index ade6af52..6561fc3b 100644 --- a/examples/nrf52840/src/bin/usb_serial_winusb.rs +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -12,6 +12,7 @@ use embassy_nrf::{interrupt, pac}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::msos::{self, windows_version}; +use embassy_usb::types::InterfaceNumber; use embassy_usb::{Builder, Config}; use {defmt_rtt as _, panic_probe as _}; @@ -77,7 +78,7 @@ async fn main(_spawner: Spawner) { // Inside a class constructor, you would just need to call `FunctionBuilder::msos_feature` instead. let msos_writer = builder.msos_writer(); msos_writer.configuration(0); - msos_writer.function(0); + msos_writer.function(InterfaceNumber(0)); msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new( "DeviceInterfaceGUIDs", From da6b1e8399cf977698e4a630f0862f698cba2b83 Mon Sep 17 00:00:00 2001 From: Lucas Granberg Date: Wed, 8 Feb 2023 17:52:49 +0200 Subject: [PATCH 45/95] Reset rng in case of clock or seed error --- embassy-stm32/src/rng.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index 10fc4a75..c487b759 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -90,8 +90,10 @@ impl<'d, T: Instance> Rng<'d, T> { impl<'d, T: Instance> RngCore for Rng<'d, T> { fn next_u32(&mut self) -> u32 { loop { - let bits = unsafe { T::regs().sr().read() }; - if bits.drdy() { + let sr = unsafe { T::regs().sr().read() }; + if sr.seis() | sr.ceis() { + self.reset(); + } else if sr.drdy() { return unsafe { T::regs().dr().read() }; } } From bab4277a8620e9a5e2347ff919d677e9c87528c9 Mon Sep 17 00:00:00 2001 From: Lucas Granberg Date: Wed, 8 Feb 2023 17:57:37 +0200 Subject: [PATCH 46/95] hack for STM32WL, rcc reset in case of seed error The STM32WL series has a more complicated rng device that gets stuck when there is a seed error. --- embassy-stm32/src/rng.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index c487b759..abc62583 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -32,6 +32,11 @@ impl<'d, T: Instance> Rng<'d, T> { } pub fn reset(&mut self) { + //stm32wl gets stuck if there is a seed error + #[cfg(stm32wl)] + if unsafe { T::regs().sr().read().seis()} { + T::reset(); + } unsafe { T::regs().cr().modify(|reg| { reg.set_rngen(true); From ac3e225988f1666eaa773a20561cbf40a3295613 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 8 Feb 2023 17:12:22 +0100 Subject: [PATCH 47/95] Update Rust nightly. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f2adc435..da75fa53 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-11-22" +channel = "nightly-2023-02-07" components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] targets = [ "thumbv7em-none-eabi", From 5edb3052e6e7c26e8b936ca1633e32d18c4bd67c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 8 Feb 2023 17:52:02 +0100 Subject: [PATCH 48/95] net: reexport driver crate. --- embassy-net/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 8d0119f6..0f694ee7 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -8,7 +8,9 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -pub mod device; +pub use embassy_net_driver as driver; + +mod device; #[cfg(feature = "tcp")] pub mod tcp; #[cfg(feature = "udp")] From ab4b3fa96dc320f034f5437c9540897f553519be Mon Sep 17 00:00:00 2001 From: Lucas Granberg Date: Thu, 9 Feb 2023 12:42:57 +0200 Subject: [PATCH 49/95] update stm32-data to include rng_v2 --- stm32-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stm32-data b/stm32-data index cc93f9d1..66252982 160000 --- a/stm32-data +++ b/stm32-data @@ -1 +1 @@ -Subproject commit cc93f9d10395077770bebefb6b9488e06b0e5811 +Subproject commit 66252982939014e94fc4a1b7423c30c3d108ae0b From 43d018b67ff98a68e7d1d8c122f803a3d90c9381 Mon Sep 17 00:00:00 2001 From: Lucas Granberg Date: Thu, 9 Feb 2023 12:44:20 +0200 Subject: [PATCH 50/95] Use rng_v2 cfg instead of chip specific for seed error recover hack --- embassy-stm32/src/rng.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index abc62583..cd3cc94c 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -32,8 +32,7 @@ impl<'d, T: Instance> Rng<'d, T> { } pub fn reset(&mut self) { - //stm32wl gets stuck if there is a seed error - #[cfg(stm32wl)] + #[cfg(rng_v2)] if unsafe { T::regs().sr().read().seis()} { T::reset(); } From 2b6654541d2d195636cf7a9c5ef3fdced0583e21 Mon Sep 17 00:00:00 2001 From: Lucas Granberg Date: Thu, 9 Feb 2023 13:01:44 +0200 Subject: [PATCH 51/95] rustfmt --- embassy-stm32/src/rng.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index cd3cc94c..1e16b847 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -32,8 +32,9 @@ impl<'d, T: Instance> Rng<'d, T> { } pub fn reset(&mut self) { + // rng_v2 locks up on seed error, needs reset #[cfg(rng_v2)] - if unsafe { T::regs().sr().read().seis()} { + if unsafe { T::regs().sr().read().seis() } { T::reset(); } unsafe { From a4371e9544f284927aae3b0de0ff5318f81acc80 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 9 Feb 2023 19:22:06 -0500 Subject: [PATCH 52/95] Add from_hz function for Duration. --- embassy-time/src/duration.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs index d3c6f42a..846a9c3d 100644 --- a/embassy-time/src/duration.rs +++ b/embassy-time/src/duration.rs @@ -81,6 +81,11 @@ impl Duration { } } + /// Creates a duration corresponding to the specified Hz. + pub const fn from_hz(hz: u64) -> Duration { + Duration { ticks: TICK_HZ / hz } + } + /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. pub fn checked_add(self, rhs: Duration) -> Option { self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) From bd7b3bd455fc5946a3944bd931acfdf255929cb6 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Thu, 9 Feb 2023 20:57:27 -0500 Subject: [PATCH 53/95] Clamp ticks to 1 and round to nearest. --- embassy-time/src/duration.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs index 846a9c3d..9d0bab2d 100644 --- a/embassy-time/src/duration.rs +++ b/embassy-time/src/duration.rs @@ -82,8 +82,17 @@ impl Duration { } /// Creates a duration corresponding to the specified Hz. + /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1 + /// tick. Doing so will not deadlock, but will certainly not produce the desired output. pub const fn from_hz(hz: u64) -> Duration { - Duration { ticks: TICK_HZ / hz } + let ticks = { + if hz >= TICK_HZ { + 1 + } else { + (TICK_HZ + hz / 2) / hz + } + }; + Duration { ticks } } /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. From 9cfea693edec5af17ba698f64b3f0a168ad92944 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 31 Jan 2023 22:06:41 +0100 Subject: [PATCH 54/95] Add DNS socket to embassy-net --- embassy-net/Cargo.toml | 4 +- embassy-net/src/dns.rs | 114 ++++++++++++++++++++++++++++++++ embassy-net/src/lib.rs | 2 + examples/std/Cargo.toml | 2 +- examples/std/src/bin/net_dns.rs | 102 ++++++++++++++++++++++++++++ 5 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 embassy-net/src/dns.rs create mode 100644 examples/std/src/bin/net_dns.rs diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 4ec340b7..6b346828 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -13,7 +13,7 @@ target = "thumbv7em-none-eabi" [features] default = [] -std = [] +std = ["smoltcp/alloc", "managed/std"] defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"] @@ -22,7 +22,7 @@ unstable-traits = [] udp = ["smoltcp/socket-udp"] tcp = ["smoltcp/socket-tcp"] -dns = ["smoltcp/socket-dns"] +dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"] proto-ipv6 = ["smoltcp/proto-ipv6"] medium-ethernet = ["smoltcp/medium-ethernet"] diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs new file mode 100644 index 00000000..f18750cc --- /dev/null +++ b/embassy-net/src/dns.rs @@ -0,0 +1,114 @@ +//! DNS socket with async support. +use core::cell::RefCell; +use core::future::poll_fn; +use core::mem; +use core::task::Poll; + +use embassy_net_driver::Driver; +use heapless::Vec; +use managed::ManagedSlice; +use smoltcp::iface::{Interface, SocketHandle}; +pub use smoltcp::socket::dns::DnsQuery; +use smoltcp::socket::dns::{self, GetQueryResultError, StartQueryError, MAX_ADDRESS_COUNT}; +pub use smoltcp::wire::{DnsQueryType, IpAddress}; + +use crate::{SocketStack, Stack}; + +/// Errors returned by DnsSocket. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// No available query slot + NoFreeSlot, + /// Invalid name + InvalidName, + /// Name too long + NameTooLong, + /// Name lookup failed + Failed, +} + +impl From for Error { + fn from(_: GetQueryResultError) -> Self { + Self::Failed + } +} + +impl From for Error { + fn from(e: StartQueryError) -> Self { + match e { + StartQueryError::NoFreeSlot => Self::NoFreeSlot, + StartQueryError::InvalidName => Self::InvalidName, + StartQueryError::NameTooLong => Self::NameTooLong, + } + } +} + +/// Async socket for making DNS queries. +pub struct DnsSocket<'a> { + stack: &'a RefCell, + handle: SocketHandle, +} + +impl<'a> DnsSocket<'a> { + /// Create a new DNS socket using the provided stack and query storage. + /// + /// DNS servers are derived from the stack configuration. + /// + /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. + pub fn new(stack: &'a Stack, queries: Q) -> Self + where + D: Driver + 'static, + Q: Into>>, + { + let servers = stack + .config() + .map(|c| { + let v: Vec = c.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); + v + }) + .unwrap_or(Vec::new()); + let s = &mut *stack.socket.borrow_mut(); + let queries: ManagedSlice<'static, Option> = unsafe { mem::transmute(queries.into()) }; + + let handle = s.sockets.add(dns::Socket::new(&servers[..], queries)); + Self { + stack: &stack.socket, + handle, + } + } + + fn with_mut(&mut self, f: impl FnOnce(&mut dns::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 + } + + /// Make a query for a given name and return the corresponding IP addresses. + pub async fn query(&mut self, name: &str, qtype: DnsQueryType) -> Result, Error> { + let query = match { self.with_mut(|s, i| s.start_query(i.context(), name, qtype)) } { + Ok(handle) => handle, + Err(e) => return Err(e.into()), + }; + + poll_fn(|cx| { + self.with_mut(|s, _| match s.get_query_result(query) { + Ok(addrs) => Poll::Ready(Ok(addrs)), + Err(GetQueryResultError::Pending) => { + s.register_query_waker(query, cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e.into())), + }) + }) + .await + } +} + +impl<'a> Drop for DnsSocket<'a> { + fn drop(&mut self) { + self.stack.borrow_mut().sockets.remove(self.handle); + } +} diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 0f694ee7..ae447d06 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -11,6 +11,8 @@ pub(crate) mod fmt; pub use embassy_net_driver as driver; mod device; +#[cfg(feature = "dns")] +pub mod dns; #[cfg(feature = "tcp")] pub mod tcp; #[cfg(feature = "udp")] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index af1481e0..8087df09 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["log"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["log", "std", "nightly", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["log", "std", "nightly"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dhcpv4"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dns", "dhcpv4", "unstable-traits", "proto-ipv6"] } embassy-net-driver = { version = "0.1.0", path = "../../embassy-net-driver" } embedded-io = { version = "0.4.0", features = ["async", "std", "futures"] } critical-section = { version = "1.1", features = ["std"] } diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs new file mode 100644 index 00000000..6203f837 --- /dev/null +++ b/examples/std/src/bin/net_dns.rs @@ -0,0 +1,102 @@ +#![feature(type_alias_impl_trait)] + +use std::default::Default; + +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::dns::{DnsQueryType, DnsSocket}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::StaticCell; + +#[path = "../tuntap.rs"] +mod tuntap; + +use crate::tuntap::TunTapDevice; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + STATIC_CELL.init_with(move || $val) + }}; +} + +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// TAP device name + #[clap(long, default_value = "tap0")] + tap: String, + /// use a static IP instead of DHCP + #[clap(long)] + static_ip: bool, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Init network device + let device = TunTapDevice::new(&opts.tap).unwrap(); + + // Choose between dhcp or static ip + let config = if opts.static_ip { + Config::Static(embassy_net::StaticConfig { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 1), 24), + dns_servers: Vec::from_slice(&[Ipv4Address::new(8, 8, 4, 4).into(), Ipv4Address::new(8, 8, 8, 8).into()]) + .unwrap(), + gateway: Some(Ipv4Address::new(192, 168, 69, 100)), + }) + } else { + Config::Dhcp(Default::default()) + }; + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + + // Then we can use it! + + let mut socket = DnsSocket::new(stack, vec![]); + + let host = "example.com"; + info!("querying host {:?}...", host); + match socket.query(host, DnsQueryType::A).await { + Ok(r) => { + info!("query response: {:?}", r); + } + Err(e) => { + warn!("query error: {:?}", e); + } + }; +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} From c203cefe0103ae150be514875b6b110b1f9a3a90 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 6 Feb 2023 20:18:12 +0100 Subject: [PATCH 55/95] Handle cancellation --- embassy-net/Cargo.toml | 1 + embassy-net/src/dns.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 6b346828..6eea8c30 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -40,6 +40,7 @@ smoltcp = { version = "0.9.0", default-features = false, features = [ ]} embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } +embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" } embassy-time = { version = "0.1.0", path = "../embassy-time" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } embedded-io = { version = "0.4.0", optional = true } diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index f18750cc..2d2e7b79 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -11,6 +11,7 @@ use smoltcp::iface::{Interface, SocketHandle}; pub use smoltcp::socket::dns::DnsQuery; use smoltcp::socket::dns::{self, GetQueryResultError, StartQueryError, MAX_ADDRESS_COUNT}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; +use embassy_hal_common::drop::OnDrop; use crate::{SocketStack, Stack}; @@ -93,7 +94,15 @@ impl<'a> DnsSocket<'a> { Err(e) => return Err(e.into()), }; - poll_fn(|cx| { + let handle = self.handle; + let drop = OnDrop::new(|| { + let s = &mut *self.stack.borrow_mut(); + let socket = s.sockets.get_mut::(handle); + socket.cancel_query(query); + s.waker.wake(); + }); + + let res = poll_fn(|cx| { self.with_mut(|s, _| match s.get_query_result(query) { Ok(addrs) => Poll::Ready(Ok(addrs)), Err(GetQueryResultError::Pending) => { @@ -103,7 +112,10 @@ impl<'a> DnsSocket<'a> { Err(e) => Poll::Ready(Err(e.into())), }) }) - .await + .await; + + drop.defuse(); + res } } From 614740a1b2ea1cada16e3391cb86d0394f64edfc Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 6 Feb 2023 20:36:08 +0100 Subject: [PATCH 56/95] cargo fmt --- embassy-net/src/dns.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 2d2e7b79..e98247bf 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -4,6 +4,7 @@ use core::future::poll_fn; use core::mem; use core::task::Poll; +use embassy_hal_common::drop::OnDrop; use embassy_net_driver::Driver; use heapless::Vec; use managed::ManagedSlice; @@ -11,7 +12,6 @@ use smoltcp::iface::{Interface, SocketHandle}; pub use smoltcp::socket::dns::DnsQuery; use smoltcp::socket::dns::{self, GetQueryResultError, StartQueryError, MAX_ADDRESS_COUNT}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; -use embassy_hal_common::drop::OnDrop; use crate::{SocketStack, Stack}; From cd440a49d677f7dfc09e405d99b87a49fba9ba31 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 17:43:23 +0100 Subject: [PATCH 57/95] Rewrite to use a single socket --- embassy-net/Cargo.toml | 2 +- embassy-net/src/dns.rs | 124 +++++++++++++------------------- embassy-net/src/lib.rs | 83 ++++++++++++++++++++- examples/std/src/bin/net_dns.rs | 3 +- 4 files changed, 133 insertions(+), 79 deletions(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 6eea8c30..53778899 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -52,5 +52,5 @@ generic-array = { version = "0.14.4", default-features = false } stable_deref_trait = { version = "1.2.0", default-features = false } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } atomic-pool = "1.0" -embedded-nal-async = { version = "0.3.0", optional = true } +embedded-nal-async = { version = "0.4.0", optional = true } atomic-polyfill = { version = "1.0" } diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index e98247bf..1815d258 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -1,19 +1,10 @@ //! DNS socket with async support. -use core::cell::RefCell; -use core::future::poll_fn; -use core::mem; -use core::task::Poll; - -use embassy_hal_common::drop::OnDrop; -use embassy_net_driver::Driver; use heapless::Vec; -use managed::ManagedSlice; -use smoltcp::iface::{Interface, SocketHandle}; -pub use smoltcp::socket::dns::DnsQuery; -use smoltcp::socket::dns::{self, GetQueryResultError, StartQueryError, MAX_ADDRESS_COUNT}; +pub use smoltcp::socket::dns::{DnsQuery, Socket, MAX_ADDRESS_COUNT}; +pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; -use crate::{SocketStack, Stack}; +use crate::{Driver, Stack}; /// Errors returned by DnsSocket. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -46,81 +37,64 @@ impl From for Error { } /// Async socket for making DNS queries. -pub struct DnsSocket<'a> { - stack: &'a RefCell, - handle: SocketHandle, +pub struct DnsSocket<'a, D> +where + D: Driver + 'static, +{ + stack: &'a Stack, } -impl<'a> DnsSocket<'a> { +impl<'a, D> DnsSocket<'a, D> +where + D: Driver + 'static, +{ /// Create a new DNS socket using the provided stack and query storage. /// /// DNS servers are derived from the stack configuration. /// /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. - pub fn new(stack: &'a Stack, queries: Q) -> Self - where - D: Driver + 'static, - Q: Into>>, - { - let servers = stack - .config() - .map(|c| { - let v: Vec = c.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); - v - }) - .unwrap_or(Vec::new()); - let s = &mut *stack.socket.borrow_mut(); - let queries: ManagedSlice<'static, Option> = unsafe { mem::transmute(queries.into()) }; - - let handle = s.sockets.add(dns::Socket::new(&servers[..], queries)); - Self { - stack: &stack.socket, - handle, - } - } - - fn with_mut(&mut self, f: impl FnOnce(&mut dns::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 + pub fn new(stack: &'a Stack) -> Self { + Self { stack } } /// Make a query for a given name and return the corresponding IP addresses. - pub async fn query(&mut self, name: &str, qtype: DnsQueryType) -> Result, Error> { - let query = match { self.with_mut(|s, i| s.start_query(i.context(), name, qtype)) } { - Ok(handle) => handle, - Err(e) => return Err(e.into()), + pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result, Error> { + self.stack.dns_query(name, qtype).await + } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D> +where + D: Driver + 'static, +{ + type Error = Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + use embedded_nal_async::{AddrType, IpAddr}; + let qtype = match addr_type { + AddrType::IPv6 => DnsQueryType::Aaaa, + _ => DnsQueryType::A, }; - - let handle = self.handle; - let drop = OnDrop::new(|| { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(handle); - socket.cancel_query(query); - s.waker.wake(); - }); - - let res = poll_fn(|cx| { - self.with_mut(|s, _| match s.get_query_result(query) { - Ok(addrs) => Poll::Ready(Ok(addrs)), - Err(GetQueryResultError::Pending) => { - s.register_query_waker(query, cx.waker()); - Poll::Pending - } - Err(e) => Poll::Ready(Err(e.into())), + let addrs = self.query(host, qtype).await?; + if let Some(first) = addrs.get(0) { + Ok(match first { + IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()), + IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()), }) - }) - .await; + } else { + Err(Error::Failed) + } + } - drop.defuse(); - res - } -} - -impl<'a> Drop for DnsSocket<'a> { - fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + async fn get_host_by_address( + &self, + _addr: embedded_nal_async::IpAddr, + ) -> Result, Self::Error> { + todo!() } } diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index ae447d06..b63aa83d 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -48,15 +48,22 @@ use crate::device::DriverAdapter; const LOCAL_PORT_MIN: u16 = 1025; const LOCAL_PORT_MAX: u16 = 65535; +const MAX_QUERIES: usize = 2; pub struct StackResources { sockets: [SocketStorage<'static>; SOCK], + #[cfg(feature = "dns")] + queries: Option<[Option; MAX_QUERIES]>, } impl StackResources { pub fn new() -> Self { + #[cfg(feature = "dns")] + const INIT: Option = None; Self { sockets: [SocketStorage::EMPTY; SOCK], + #[cfg(feature = "dns")] + queries: Some([INIT; MAX_QUERIES]), } } } @@ -109,6 +116,8 @@ struct Inner { config: Option, #[cfg(feature = "dhcpv4")] dhcp_socket: Option, + #[cfg(feature = "dns")] + dns_socket: Option, } pub(crate) struct SocketStack { @@ -153,6 +162,8 @@ impl Stack { config: None, #[cfg(feature = "dhcpv4")] dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket: None, }; let mut socket = SocketStack { sockets, @@ -161,8 +172,17 @@ impl Stack { next_local_port, }; + #[cfg(feature = "dns")] + { + if let Some(queries) = resources.queries.take() { + inner.dns_socket = Some(socket.sockets.add(dns::Socket::new(&[], queries))); + } + } + match config { - Config::Static(config) => inner.apply_config(&mut socket, config), + Config::Static(config) => { + inner.apply_config(&mut socket, config); + } #[cfg(feature = "dhcpv4")] Config::Dhcp(config) => { let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new(); @@ -210,6 +230,59 @@ impl Stack { .await; unreachable!() } + + #[cfg(feature = "dns")] + async fn dns_query( + &self, + name: &str, + qtype: dns::DnsQueryType, + ) -> Result, dns::Error> { + let query = self.with_mut(|s, i| { + if let Some(dns_handle) = i.dns_socket { + let socket = s.sockets.get_mut::(dns_handle); + match socket.start_query(s.iface.context(), name, qtype) { + Ok(handle) => Ok(handle), + Err(e) => Err(e.into()), + } + } else { + Err(dns::Error::Failed) + } + })?; + + use embassy_hal_common::drop::OnDrop; + let drop = OnDrop::new(|| { + self.with_mut(|s, i| { + if let Some(dns_handle) = i.dns_socket { + let socket = s.sockets.get_mut::(dns_handle); + socket.cancel_query(query); + s.waker.wake(); + } + }) + }); + + let res = poll_fn(|cx| { + self.with_mut(|s, i| { + if let Some(dns_handle) = i.dns_socket { + let socket = s.sockets.get_mut::(dns_handle); + match socket.get_query_result(query) { + Ok(addrs) => Poll::Ready(Ok(addrs)), + Err(dns::GetQueryResultError::Pending) => { + socket.register_query_waker(query, cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e.into())), + } + } else { + Poll::Ready(Err(dns::Error::Failed)) + } + }) + }) + .await; + + drop.defuse(); + + res + } } impl SocketStack { @@ -251,6 +324,13 @@ impl Inner { debug!(" DNS server {}: {}", i, s); } + #[cfg(feature = "dns")] + if let Some(dns_socket) = self.dns_socket { + let socket = s.sockets.get_mut::(dns_socket); + let servers: Vec = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); + socket.update_servers(&servers[..]); + } + self.config = Some(config) } @@ -326,6 +406,7 @@ impl Inner { //if old_link_up || self.link_up { // self.poll_configurator(timestamp) //} + // if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { let t = Timer::at(instant_from_smoltcp(poll_at)); diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs index 6203f837..e787cb82 100644 --- a/examples/std/src/bin/net_dns.rs +++ b/examples/std/src/bin/net_dns.rs @@ -71,8 +71,7 @@ async fn main_task(spawner: Spawner) { spawner.spawn(net_task(stack)).unwrap(); // Then we can use it! - - let mut socket = DnsSocket::new(stack, vec![]); + let socket = DnsSocket::new(stack); let host = "example.com"; info!("querying host {:?}...", host); From 7ae47cb1d81d5112fdc42ceaec470350aab5c372 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 18:20:50 +0100 Subject: [PATCH 58/95] Expose api on Stack and add doc Make it work with smoltcp 0.9 --- embassy-net/src/dns.rs | 8 +++----- embassy-net/src/lib.rs | 7 ++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 1815d258..9b1b936c 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -1,6 +1,6 @@ //! DNS socket with async support. use heapless::Vec; -pub use smoltcp::socket::dns::{DnsQuery, Socket, MAX_ADDRESS_COUNT}; +pub use smoltcp::socket::dns::{DnsQuery, Socket}; pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; @@ -48,9 +48,7 @@ impl<'a, D> DnsSocket<'a, D> where D: Driver + 'static, { - /// Create a new DNS socket using the provided stack and query storage. - /// - /// DNS servers are derived from the stack configuration. + /// 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 { @@ -58,7 +56,7 @@ where } /// Make a query for a given name and return the corresponding IP addresses. - pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result, Error> { + pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result, Error> { self.stack.dns_query(name, qtype).await } } diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index b63aa83d..5b6ab0e3 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -231,12 +231,9 @@ impl Stack { unreachable!() } + /// Make a query for a given name and return the corresponding IP addresses. #[cfg(feature = "dns")] - async fn dns_query( - &self, - name: &str, - qtype: dns::DnsQueryType, - ) -> Result, dns::Error> { + pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result, dns::Error> { let query = self.with_mut(|s, i| { if let Some(dns_handle) = i.dns_socket { let socket = s.sockets.get_mut::(dns_handle); From 6e68353a931cc268f61b4606c3a99bf8f5e96f6e Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 18:30:17 +0100 Subject: [PATCH 59/95] attempt removing option --- embassy-net/src/lib.rs | 76 ++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 5b6ab0e3..627c0a0f 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -53,7 +53,7 @@ const MAX_QUERIES: usize = 2; pub struct StackResources { sockets: [SocketStorage<'static>; SOCK], #[cfg(feature = "dns")] - queries: Option<[Option; MAX_QUERIES]>, + queries: [Option; MAX_QUERIES], } impl StackResources { @@ -63,7 +63,7 @@ impl StackResources { Self { sockets: [SocketStorage::EMPTY; SOCK], #[cfg(feature = "dns")] - queries: Some([INIT; MAX_QUERIES]), + queries: [INIT; MAX_QUERIES], } } } @@ -117,7 +117,7 @@ struct Inner { #[cfg(feature = "dhcpv4")] dhcp_socket: Option, #[cfg(feature = "dns")] - dns_socket: Option, + dns_socket: SocketHandle, } pub(crate) struct SocketStack { @@ -156,15 +156,7 @@ impl Stack { let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; - let mut inner = Inner { - device, - link_up: false, - config: None, - #[cfg(feature = "dhcpv4")] - dhcp_socket: None, - #[cfg(feature = "dns")] - dns_socket: None, - }; + let mut socket = SocketStack { sockets, iface, @@ -172,12 +164,15 @@ impl Stack { next_local_port, }; - #[cfg(feature = "dns")] - { - if let Some(queries) = resources.queries.take() { - inner.dns_socket = Some(socket.sockets.add(dns::Socket::new(&[], queries))); - } - } + let mut inner = Inner { + device, + link_up: false, + config: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket: socket.sockets.add(dns::Socket::new(&[], &mut resources.queries)), + }; match config { Config::Static(config) => { @@ -235,42 +230,29 @@ impl Stack { #[cfg(feature = "dns")] pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result, dns::Error> { let query = self.with_mut(|s, i| { - if let Some(dns_handle) = i.dns_socket { - let socket = s.sockets.get_mut::(dns_handle); - match socket.start_query(s.iface.context(), name, qtype) { - Ok(handle) => Ok(handle), - Err(e) => Err(e.into()), - } - } else { - Err(dns::Error::Failed) - } + let socket = s.sockets.get_mut::(i.dns_socket); + socket.start_query(s.iface.context(), name, qtype) })?; use embassy_hal_common::drop::OnDrop; let drop = OnDrop::new(|| { self.with_mut(|s, i| { - if let Some(dns_handle) = i.dns_socket { - let socket = s.sockets.get_mut::(dns_handle); - socket.cancel_query(query); - s.waker.wake(); - } + let socket = s.sockets.get_mut::(i.dns_socket); + socket.cancel_query(query); + s.waker.wake(); }) }); let res = poll_fn(|cx| { self.with_mut(|s, i| { - if let Some(dns_handle) = i.dns_socket { - let socket = s.sockets.get_mut::(dns_handle); - match socket.get_query_result(query) { - Ok(addrs) => Poll::Ready(Ok(addrs)), - Err(dns::GetQueryResultError::Pending) => { - socket.register_query_waker(query, cx.waker()); - Poll::Pending - } - Err(e) => Poll::Ready(Err(e.into())), + let socket = s.sockets.get_mut::(i.dns_socket); + match socket.get_query_result(query) { + Ok(addrs) => Poll::Ready(Ok(addrs)), + Err(dns::GetQueryResultError::Pending) => { + socket.register_query_waker(query, cx.waker()); + Poll::Pending } - } else { - Poll::Ready(Err(dns::Error::Failed)) + Err(e) => Poll::Ready(Err(e.into())), } }) }) @@ -322,11 +304,9 @@ impl Inner { } #[cfg(feature = "dns")] - if let Some(dns_socket) = self.dns_socket { - let socket = s.sockets.get_mut::(dns_socket); - let servers: Vec = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); - socket.update_servers(&servers[..]); - } + let socket = s.sockets.get_mut::(self.dns_socket); + let servers: Vec = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); + socket.update_servers(&servers[..]); self.config = Some(config) } From 472473d8c1cfc5985ee81fa8dac88c46b8fa64a9 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 18:32:35 +0100 Subject: [PATCH 60/95] Create slice using ::Owned --- embassy-net/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 627c0a0f..25a7891c 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -171,7 +171,7 @@ impl Stack { #[cfg(feature = "dhcpv4")] dhcp_socket: None, #[cfg(feature = "dns")] - dns_socket: socket.sockets.add(dns::Socket::new(&[], &mut resources.queries)), + dns_socket: socket.sockets.add(dns::Socket::new(&[], managed::ManagedSlice::Borrowed(&mut resources.queries))), }; match config { From 48dff04d647a5303b9ed0a85ecce2f8a8f17ba0f Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 18:34:21 +0100 Subject: [PATCH 61/95] Bump max queries --- embassy-net/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 25a7891c..e5f7479c 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -48,7 +48,8 @@ use crate::device::DriverAdapter; const LOCAL_PORT_MIN: u16 = 1025; const LOCAL_PORT_MAX: u16 = 65535; -const MAX_QUERIES: usize = 2; +#[cfg(feature = "dns")] +const MAX_QUERIES: usize = 4; pub struct StackResources { sockets: [SocketStorage<'static>; SOCK], From 32c3725631b72807b926187d29916468fed68c81 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 18:44:51 +0100 Subject: [PATCH 62/95] add waker for DNS slots --- embassy-net/src/dns.rs | 4 +--- embassy-net/src/lib.rs | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 9b1b936c..2dd44a4e 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -10,8 +10,6 @@ use crate::{Driver, Stack}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { - /// No available query slot - NoFreeSlot, /// Invalid name InvalidName, /// Name too long @@ -29,7 +27,7 @@ impl From for Error { impl From for Error { fn from(e: StartQueryError) -> Self { match e { - StartQueryError::NoFreeSlot => Self::NoFreeSlot, + StartQueryError::NoFreeSlot => Self::Failed, StartQueryError::InvalidName => Self::InvalidName, StartQueryError::NameTooLong => Self::NameTooLong, } diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index e5f7479c..9d9de913 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -119,6 +119,8 @@ struct Inner { dhcp_socket: Option, #[cfg(feature = "dns")] dns_socket: SocketHandle, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration, } pub(crate) struct SocketStack { @@ -157,7 +159,6 @@ impl Stack { let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; - let mut socket = SocketStack { sockets, iface, @@ -172,7 +173,12 @@ impl Stack { #[cfg(feature = "dhcpv4")] dhcp_socket: None, #[cfg(feature = "dns")] - dns_socket: socket.sockets.add(dns::Socket::new(&[], managed::ManagedSlice::Borrowed(&mut resources.queries))), + dns_socket: socket.sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(&mut resources.queries), + )), + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), }; match config { @@ -230,10 +236,20 @@ impl Stack { /// Make a query for a given name and return the corresponding IP addresses. #[cfg(feature = "dns")] pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result, dns::Error> { - let query = self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); - socket.start_query(s.iface.context(), name, qtype) - })?; + 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) { + Ok(handle) => Poll::Ready(Ok(handle)), + Err(dns::StartQueryError::NoFreeSlot) => { + i.dns_waker.register(cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e)), + } + }) + }) + .await?; use embassy_hal_common::drop::OnDrop; let drop = OnDrop::new(|| { @@ -241,6 +257,7 @@ impl Stack { let socket = s.sockets.get_mut::(i.dns_socket); socket.cancel_query(query); s.waker.wake(); + i.dns_waker.wake(); }) }); @@ -248,12 +265,18 @@ impl Stack { self.with_mut(|s, i| { let socket = s.sockets.get_mut::(i.dns_socket); match socket.get_query_result(query) { - Ok(addrs) => Poll::Ready(Ok(addrs)), + Ok(addrs) => { + i.dns_waker.wake(); + Poll::Ready(Ok(addrs)) + } Err(dns::GetQueryResultError::Pending) => { socket.register_query_waker(query, cx.waker()); Poll::Pending } - Err(e) => Poll::Ready(Err(e.into())), + Err(e) => { + i.dns_waker.wake(); + Poll::Ready(Err(e.into())) + } } }) }) From a7d3ef9122e45a808e1c401b33ee380a0c7c867d Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 19:00:00 +0100 Subject: [PATCH 63/95] scope dns operations within a cfged block --- embassy-net/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 9d9de913..bda5f9e1 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -328,9 +328,11 @@ impl Inner { } #[cfg(feature = "dns")] - let socket = s.sockets.get_mut::(self.dns_socket); - let servers: Vec = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); - socket.update_servers(&servers[..]); + { + let socket = s.sockets.get_mut::(self.dns_socket); + let servers: Vec = config.dns_servers.iter().map(|c| IpAddress::Ipv4(*c)).collect(); + socket.update_servers(&servers[..]); + } self.config = Some(config) } From 128a453163acbdd2f3e3e320bbd06eb38f196601 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 19:04:54 +0100 Subject: [PATCH 64/95] remove unneeded features --- embassy-net/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 53778899..2d8c5c7b 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -13,7 +13,7 @@ target = "thumbv7em-none-eabi" [features] default = [] -std = ["smoltcp/alloc", "managed/std"] +std = [] defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"] From a2b8921ff3e0b50da824c2242f0b90f9f8f9a0e2 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 10 Feb 2023 19:38:17 +0100 Subject: [PATCH 65/95] fix: cfg guard for ipv6 --- embassy-net/src/dns.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 2dd44a4e..97320e44 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -80,6 +80,7 @@ where if let Some(first) = addrs.get(0) { Ok(match first { IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()), + #[cfg(feature = "proto-ipv6")] IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()), }) } else { From a509af4bc00ae6945e568b268731e854e8ae3994 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 10 Feb 2023 23:00:16 +0100 Subject: [PATCH 66/95] exmaples/dns: don't use the socket. --- examples/std/src/bin/net_dns.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs index e787cb82..e1cc45a3 100644 --- a/examples/std/src/bin/net_dns.rs +++ b/examples/std/src/bin/net_dns.rs @@ -4,7 +4,7 @@ use std::default::Default; use clap::Parser; use embassy_executor::{Executor, Spawner}; -use embassy_net::dns::{DnsQueryType, DnsSocket}; +use embassy_net::dns::DnsQueryType; use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; use heapless::Vec; use log::*; @@ -65,17 +65,14 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + let stack: &Stack<_> = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); // Launch network task spawner.spawn(net_task(stack)).unwrap(); - // Then we can use it! - let socket = DnsSocket::new(stack); - let host = "example.com"; info!("querying host {:?}...", host); - match socket.query(host, DnsQueryType::A).await { + match stack.dns_query(host, DnsQueryType::A).await { Ok(r) => { info!("query response: {:?}", r); } From 4c4e923e057cc376ae3ebc2c2f7a01ed4723d308 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 10 Feb 2023 22:47:17 +0100 Subject: [PATCH 67/95] nrf/qspi: do not panic when canceling futures. --- embassy-nrf/src/qspi.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index 07a97001..d434327f 100644 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -6,7 +6,7 @@ use core::future::poll_fn; use core::ptr; use core::task::Poll; -use embassy_hal_common::drop::DropBomb; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::{self, Pin as GpioPin}; @@ -190,7 +190,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { // Enable it r.enable.write(|w| w.enable().enabled()); - let mut res = Self { + let res = Self { dpm_enabled: config.deep_power_down.is_some(), irq, }; @@ -200,7 +200,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { r.tasks_activate.write(|w| w.tasks_activate().bit(true)); - res.blocking_wait_ready(); + Self::blocking_wait_ready(); res } @@ -217,7 +217,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { /// Do a custom QSPI instruction. pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; @@ -226,7 +226,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { self.custom_instruction_finish(resp)?; - bomb.defuse(); + ondrop.defuse(); Ok(()) } @@ -236,7 +236,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); self.custom_instruction_finish(resp)?; @@ -312,7 +312,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { .await } - fn blocking_wait_ready(&mut self) { + fn blocking_wait_ready() { loop { let r = T::regs(); if r.events_ready.read().bits() != 0 { @@ -382,36 +382,36 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { /// Read data from the flash memory. pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_read(address, data)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } /// Write data to the flash memory. pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_write(address, data)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } /// Erase a sector on the flash memory. pub async fn erase(&mut self, address: usize) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_erase(address)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } @@ -419,21 +419,21 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { /// Read data from the flash memory, blocking version. pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { self.start_read(address, data)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } /// Write data to the flash memory, blocking version. pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { self.start_write(address, data)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } /// Erase a sector on the flash memory, blocking version. pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> { self.start_erase(address)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } } From 76642b3a3cddaa226dcd741d5f9f8e9d01c2f3ac Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 10 Feb 2023 23:35:44 +0100 Subject: [PATCH 68/95] fix h7 examples --- examples/nrf52840/src/bin/usb_ethernet.rs | 23 +++++++++++++++++++++++ examples/stm32h7/Cargo.toml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index 97978089..430468ad 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -46,8 +46,31 @@ async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } +#[inline(never)] +pub fn test_function() -> (usize, u32, [u32; 2]) { + let mut array = [3; 2]; + + let mut index = 0; + let mut result = 0; + + for x in [1, 2] { + if x == 1 { + array[1] = 99; + } else { + index = if x == 2 { 1 } else { 0 }; + + // grabs value from array[0], not array[1] + result = array[index]; + } + } + + (index, result, array) +} + #[embassy_executor::main] async fn main(spawner: Spawner) { + info!("{:?}", test_function()); + let p = embassy_nrf::init(Default::default()); let clock: pac::CLOCK = unsafe { mem::transmute(()) }; diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index bcf97641..a0413478 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -21,7 +21,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.9" } embedded-hal-async = { version = "=0.2.0-alpha.0" } -embedded-nal-async = "0.3.0" +embedded-nal-async = "0.4.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } From d21643c060376fff0f0f5b8c5bc4eed4df4bc560 Mon Sep 17 00:00:00 2001 From: Christian Enderle Date: Sun, 12 Feb 2023 11:31:22 +0100 Subject: [PATCH 69/95] fix "prescaler none" which incorrectly set "prescaler divided by 3" --- embassy-stm32/src/rcc/wl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs index 34767491..82b0d04e 100644 --- a/embassy-stm32/src/rcc/wl.rs +++ b/embassy-stm32/src/rcc/wl.rs @@ -158,7 +158,7 @@ impl Into for APBPrescaler { impl Into for AHBPrescaler { fn into(self) -> u8 { match self { - AHBPrescaler::NotDivided => 1, + AHBPrescaler::NotDivided => 0x0, AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div4 => 0x09, From 80b7c3cf69141c76ba2ae099225c2580cd430086 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 13 Feb 2023 01:30:53 +0100 Subject: [PATCH 70/95] Fix doc build. --- .github/workflows/doc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 8a341b8f..827f8817 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: crates: - - stm32 + #- stm32 # runs out of disk space... - rest # This will ensure at most one doc build job is running at a time @@ -46,7 +46,7 @@ jobs: - name: Install docserver run: | - wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.3/builder" + wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder" chmod +x /usr/local/bin/builder - name: build-stm32 From 1e36c91bf884ed16b898bd5a79e32ee0e62471ef Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 13 Feb 2023 02:22:06 +0100 Subject: [PATCH 71/95] stm32: fix fmc-related build failures on some F4's --- embassy-stm32/src/{fmc/mod.rs => fmc.rs} | 130 +++++++++++++++++++++-- embassy-stm32/src/fmc/pins.rs | 118 -------------------- 2 files changed, 122 insertions(+), 126 deletions(-) rename embassy-stm32/src/{fmc/mod.rs => fmc.rs} (64%) delete mode 100644 embassy-stm32/src/fmc/pins.rs diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc.rs similarity index 64% rename from embassy-stm32/src/fmc/mod.rs rename to embassy-stm32/src/fmc.rs index 5adfa0d4..b9129cb5 100644 --- a/embassy-stm32/src/fmc/mod.rs +++ b/embassy-stm32/src/fmc.rs @@ -6,9 +6,6 @@ use crate::gpio::sealed::AFType; use crate::gpio::{Pull, Speed}; use crate::Peripheral; -mod pins; -pub use pins::*; - pub struct Fmc<'d, T: Instance> { peri: PhantomData<&'d mut T>, } @@ -19,7 +16,7 @@ unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T> where T: Instance, { - const REGISTERS: *const () = crate::pac::FMC.0 as *const _; + const REGISTERS: *const () = T::REGS.0 as *const _; fn enable(&mut self) { ::enable(); @@ -32,7 +29,7 @@ where // This is a "not" because it is expected that all future versions have this bit #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1)))] unsafe { - T::regs().bcr1().modify(|r| r.set_fmcen(true)) + T::REGS.bcr1().modify(|r| r.set_fmcen(true)) }; } @@ -150,13 +147,130 @@ impl<'d, T: Instance> Fmc<'d, T> { )); } +pub(crate) mod sealed { + pub trait Instance: crate::rcc::sealed::RccPeripheral { + const REGS: crate::pac::fmc::Fmc; + } +} + +pub trait Instance: sealed::Instance + 'static {} + foreach_peripheral!( (fmc, $inst:ident) => { impl crate::fmc::sealed::Instance for crate::peripherals::$inst { - fn regs() -> stm32_metapac::fmc::Fmc { - crate::pac::$inst - } + const REGS: crate::pac::fmc::Fmc = crate::pac::$inst; } impl crate::fmc::Instance for crate::peripherals::$inst {} }; ); + +pin_trait!(SDNWEPin, Instance); +pin_trait!(SDNCASPin, Instance); +pin_trait!(SDNRASPin, Instance); + +pin_trait!(SDNE0Pin, Instance); +pin_trait!(SDNE1Pin, Instance); + +pin_trait!(SDCKE0Pin, Instance); +pin_trait!(SDCKE1Pin, Instance); + +pin_trait!(SDCLKPin, Instance); + +pin_trait!(NBL0Pin, Instance); +pin_trait!(NBL1Pin, Instance); +pin_trait!(NBL2Pin, Instance); +pin_trait!(NBL3Pin, Instance); + +pin_trait!(INTPin, Instance); +pin_trait!(NLPin, Instance); +pin_trait!(NWaitPin, Instance); + +pin_trait!(NE1Pin, Instance); +pin_trait!(NE2Pin, Instance); +pin_trait!(NE3Pin, Instance); +pin_trait!(NE4Pin, Instance); + +pin_trait!(NCEPin, Instance); +pin_trait!(NOEPin, Instance); +pin_trait!(NWEPin, Instance); +pin_trait!(ClkPin, Instance); + +pin_trait!(BA0Pin, Instance); +pin_trait!(BA1Pin, Instance); + +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(D8Pin, Instance); +pin_trait!(D9Pin, Instance); +pin_trait!(D10Pin, Instance); +pin_trait!(D11Pin, Instance); +pin_trait!(D12Pin, Instance); +pin_trait!(D13Pin, Instance); +pin_trait!(D14Pin, Instance); +pin_trait!(D15Pin, Instance); +pin_trait!(D16Pin, Instance); +pin_trait!(D17Pin, Instance); +pin_trait!(D18Pin, Instance); +pin_trait!(D19Pin, Instance); +pin_trait!(D20Pin, Instance); +pin_trait!(D21Pin, Instance); +pin_trait!(D22Pin, Instance); +pin_trait!(D23Pin, Instance); +pin_trait!(D24Pin, Instance); +pin_trait!(D25Pin, Instance); +pin_trait!(D26Pin, Instance); +pin_trait!(D27Pin, Instance); +pin_trait!(D28Pin, Instance); +pin_trait!(D29Pin, Instance); +pin_trait!(D30Pin, Instance); +pin_trait!(D31Pin, Instance); + +pin_trait!(DA0Pin, Instance); +pin_trait!(DA1Pin, Instance); +pin_trait!(DA2Pin, Instance); +pin_trait!(DA3Pin, Instance); +pin_trait!(DA4Pin, Instance); +pin_trait!(DA5Pin, Instance); +pin_trait!(DA6Pin, Instance); +pin_trait!(DA7Pin, Instance); +pin_trait!(DA8Pin, Instance); +pin_trait!(DA9Pin, Instance); +pin_trait!(DA10Pin, Instance); +pin_trait!(DA11Pin, Instance); +pin_trait!(DA12Pin, Instance); +pin_trait!(DA13Pin, Instance); +pin_trait!(DA14Pin, Instance); +pin_trait!(DA15Pin, Instance); + +pin_trait!(A0Pin, Instance); +pin_trait!(A1Pin, Instance); +pin_trait!(A2Pin, Instance); +pin_trait!(A3Pin, Instance); +pin_trait!(A4Pin, Instance); +pin_trait!(A5Pin, Instance); +pin_trait!(A6Pin, Instance); +pin_trait!(A7Pin, Instance); +pin_trait!(A8Pin, Instance); +pin_trait!(A9Pin, Instance); +pin_trait!(A10Pin, Instance); +pin_trait!(A11Pin, Instance); +pin_trait!(A12Pin, Instance); +pin_trait!(A13Pin, Instance); +pin_trait!(A14Pin, Instance); +pin_trait!(A15Pin, Instance); +pin_trait!(A16Pin, Instance); +pin_trait!(A17Pin, Instance); +pin_trait!(A18Pin, Instance); +pin_trait!(A19Pin, Instance); +pin_trait!(A20Pin, Instance); +pin_trait!(A21Pin, Instance); +pin_trait!(A22Pin, Instance); +pin_trait!(A23Pin, Instance); +pin_trait!(A24Pin, Instance); +pin_trait!(A25Pin, Instance); diff --git a/embassy-stm32/src/fmc/pins.rs b/embassy-stm32/src/fmc/pins.rs deleted file mode 100644 index 5062e52a..00000000 --- a/embassy-stm32/src/fmc/pins.rs +++ /dev/null @@ -1,118 +0,0 @@ -pub(crate) mod sealed { - pub trait Instance: crate::rcc::sealed::RccPeripheral { - fn regs() -> crate::pac::fmc::Fmc; - } -} - -pub trait Instance: sealed::Instance + 'static {} - -pin_trait!(SDNWEPin, Instance); -pin_trait!(SDNCASPin, Instance); -pin_trait!(SDNRASPin, Instance); - -pin_trait!(SDNE0Pin, Instance); -pin_trait!(SDNE1Pin, Instance); - -pin_trait!(SDCKE0Pin, Instance); -pin_trait!(SDCKE1Pin, Instance); - -pin_trait!(SDCLKPin, Instance); - -pin_trait!(NBL0Pin, Instance); -pin_trait!(NBL1Pin, Instance); -pin_trait!(NBL2Pin, Instance); -pin_trait!(NBL3Pin, Instance); - -pin_trait!(INTPin, Instance); -pin_trait!(NLPin, Instance); -pin_trait!(NWaitPin, Instance); - -pin_trait!(NE1Pin, Instance); -pin_trait!(NE2Pin, Instance); -pin_trait!(NE3Pin, Instance); -pin_trait!(NE4Pin, Instance); - -pin_trait!(NCEPin, Instance); -pin_trait!(NOEPin, Instance); -pin_trait!(NWEPin, Instance); -pin_trait!(ClkPin, Instance); - -pin_trait!(BA0Pin, Instance); -pin_trait!(BA1Pin, Instance); - -pin_trait!(D0Pin, Instance); -pin_trait!(D1Pin, Instance); -pin_trait!(D2Pin, Instance); -pin_trait!(D3Pin, Instance); -pin_trait!(D4Pin, Instance); -pin_trait!(D5Pin, Instance); -pin_trait!(D6Pin, Instance); -pin_trait!(D7Pin, Instance); -pin_trait!(D8Pin, Instance); -pin_trait!(D9Pin, Instance); -pin_trait!(D10Pin, Instance); -pin_trait!(D11Pin, Instance); -pin_trait!(D12Pin, Instance); -pin_trait!(D13Pin, Instance); -pin_trait!(D14Pin, Instance); -pin_trait!(D15Pin, Instance); -pin_trait!(D16Pin, Instance); -pin_trait!(D17Pin, Instance); -pin_trait!(D18Pin, Instance); -pin_trait!(D19Pin, Instance); -pin_trait!(D20Pin, Instance); -pin_trait!(D21Pin, Instance); -pin_trait!(D22Pin, Instance); -pin_trait!(D23Pin, Instance); -pin_trait!(D24Pin, Instance); -pin_trait!(D25Pin, Instance); -pin_trait!(D26Pin, Instance); -pin_trait!(D27Pin, Instance); -pin_trait!(D28Pin, Instance); -pin_trait!(D29Pin, Instance); -pin_trait!(D30Pin, Instance); -pin_trait!(D31Pin, Instance); - -pin_trait!(DA0Pin, Instance); -pin_trait!(DA1Pin, Instance); -pin_trait!(DA2Pin, Instance); -pin_trait!(DA3Pin, Instance); -pin_trait!(DA4Pin, Instance); -pin_trait!(DA5Pin, Instance); -pin_trait!(DA6Pin, Instance); -pin_trait!(DA7Pin, Instance); -pin_trait!(DA8Pin, Instance); -pin_trait!(DA9Pin, Instance); -pin_trait!(DA10Pin, Instance); -pin_trait!(DA11Pin, Instance); -pin_trait!(DA12Pin, Instance); -pin_trait!(DA13Pin, Instance); -pin_trait!(DA14Pin, Instance); -pin_trait!(DA15Pin, Instance); - -pin_trait!(A0Pin, Instance); -pin_trait!(A1Pin, Instance); -pin_trait!(A2Pin, Instance); -pin_trait!(A3Pin, Instance); -pin_trait!(A4Pin, Instance); -pin_trait!(A5Pin, Instance); -pin_trait!(A6Pin, Instance); -pin_trait!(A7Pin, Instance); -pin_trait!(A8Pin, Instance); -pin_trait!(A9Pin, Instance); -pin_trait!(A10Pin, Instance); -pin_trait!(A11Pin, Instance); -pin_trait!(A12Pin, Instance); -pin_trait!(A13Pin, Instance); -pin_trait!(A14Pin, Instance); -pin_trait!(A15Pin, Instance); -pin_trait!(A16Pin, Instance); -pin_trait!(A17Pin, Instance); -pin_trait!(A18Pin, Instance); -pin_trait!(A19Pin, Instance); -pin_trait!(A20Pin, Instance); -pin_trait!(A21Pin, Instance); -pin_trait!(A22Pin, Instance); -pin_trait!(A23Pin, Instance); -pin_trait!(A24Pin, Instance); -pin_trait!(A25Pin, Instance); From 951f2089156288288b8402b2b4b1d3feacdfe975 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 13 Feb 2023 02:37:53 +0100 Subject: [PATCH 72/95] Add more crates to docs. --- .github/workflows/doc.yml | 2 ++ embassy-net-driver-channel/Cargo.toml | 6 ++++++ embassy-net-driver/Cargo.toml | 2 +- embassy-usb-logger/Cargo.toml | 5 +++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 827f8817..4bc73fdc 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -70,12 +70,14 @@ jobs: builder ./embassy-lora crates/embassy-lora/git.zup builder ./embassy-net crates/embassy-net/git.zup builder ./embassy-net-driver crates/embassy-net-driver/git.zup + builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup builder ./embassy-nrf crates/embassy-nrf/git.zup builder ./embassy-rp crates/embassy-rp/git.zup builder ./embassy-sync crates/embassy-sync/git.zup builder ./embassy-time crates/embassy-time/git.zup builder ./embassy-usb crates/embassy-usb/git.zup builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup + builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup - name: upload run: | diff --git a/embassy-net-driver-channel/Cargo.toml b/embassy-net-driver-channel/Cargo.toml index 700a4e8a..6cb6c356 100644 --- a/embassy-net-driver-channel/Cargo.toml +++ b/embassy-net-driver-channel/Cargo.toml @@ -3,6 +3,12 @@ name = "embassy-net-driver-channel" version = "0.1.0" edition = "2021" +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-net-driver/Cargo.toml b/embassy-net-driver/Cargo.toml index 7ab9d119..ff6f2935 100644 --- a/embassy-net-driver/Cargo.toml +++ b/embassy-net-driver/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net/src/" +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/" features = ["defmt"] target = "thumbv7em-none-eabi" diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml index 2f466592..18d70b0c 100644 --- a/embassy-usb-logger/Cargo.toml +++ b/embassy-usb-logger/Cargo.toml @@ -3,6 +3,11 @@ name = "embassy-usb-logger" version = "0.1.0" edition = "2021" +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-logger-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-logger/src/" +target = "thumbv7em-none-eabi" + [dependencies] embassy-usb = { version = "0.1.0", path = "../embassy-usb" } embassy-sync = { version = "0.1.0", path = "../embassy-sync" } From 4e15043fc2047751c5580e500a6cd7a0b316f8f5 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 13 Feb 2023 02:40:29 +0100 Subject: [PATCH 73/95] add stm32f413vh to CI --- ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.sh b/ci.sh index b325e376..4199f91d 100755 --- a/ci.sh +++ b/ci.sh @@ -63,6 +63,7 @@ cargo batch \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \ From 363054de983f15a9344ca58a05e41c425661d3db Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 13 Feb 2023 03:02:12 +0100 Subject: [PATCH 74/95] stm32: doc all chips. --- embassy-stm32/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 10e5e48c..82b1ba3a 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -17,7 +17,7 @@ flavors = [ { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32f42.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, From 41a563aae3e474955892b27487e185f5f486f525 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 13 Feb 2023 03:11:16 +0100 Subject: [PATCH 75/95] net: document all features --- embassy-net/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 2d8c5c7b..ca34262d 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" [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", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"] +features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip"] target = "thumbv7em-none-eabi" [features] From 1626a4a74b50d60f2f8a53f477d49cb67a140f79 Mon Sep 17 00:00:00 2001 From: Slushee <55996847+Slushee-a@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:12:50 +0000 Subject: [PATCH 76/95] Add clone to embassy_rp::gpio::Level --- 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 4abb9839..e258733a 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -16,7 +16,7 @@ const NEW_AW: AtomicWaker = AtomicWaker::new(); static INTERRUPT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; /// Represents a digital input or output level. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone)] pub enum Level { Low, High, From dfc58ad3a27d9313bebbd0ee49976afb54df772e Mon Sep 17 00:00:00 2001 From: Slushee <55996847+Slushee-a@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:29:35 +0000 Subject: [PATCH 77/95] Add copy to Level enum in embassy-rp gpio module --- 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 e258733a..76d4281f 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -16,7 +16,7 @@ const NEW_AW: AtomicWaker = AtomicWaker::new(); static INTERRUPT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; /// Represents a digital input or output level. -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Level { Low, High, From e641db1f7565c3eb46bae04b5a8c9aef01900f53 Mon Sep 17 00:00:00 2001 From: sekoia Date: Wed, 15 Feb 2023 14:10:07 +0100 Subject: [PATCH 78/95] Fix a typo in "PioPeripheral" --- embassy-rp/src/pio.rs | 4 ++-- examples/rp/src/bin/pio_async.rs | 2 +- examples/rp/src/bin/pio_dma.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-rp/src/pio.rs b/embassy-rp/src/pio.rs index 1a067e54..2fb2783d 100644 --- a/embassy-rp/src/pio.rs +++ b/embassy-rp/src/pio.rs @@ -1174,7 +1174,7 @@ impl SmInstance for SmInstanceBase { const SM_NO: u8 = SM_NO; } -pub trait PioPeripherial: Sized { +pub trait PioPeripheral: Sized { type Pio: PioInstance; fn pio(&self) -> u8 { let _ = self; @@ -1249,7 +1249,7 @@ pub type Sm3 = SmInstanceBase<3>; macro_rules! impl_pio_sm { ($name:ident, $pio:expr) => { - impl PioPeripherial for peripherals::$name { + impl PioPeripheral for peripherals::$name { type Pio = PioInstanceBase<$pio>; } }; diff --git a/examples/rp/src/bin/pio_async.rs b/examples/rp/src/bin/pio_async.rs index 45a8c73f..e616d8c5 100644 --- a/examples/rp/src/bin/pio_async.rs +++ b/examples/rp/src/bin/pio_async.rs @@ -4,7 +4,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_rp::gpio::{AnyPin, Pin}; -use embassy_rp::pio::{Pio0, PioPeripherial, PioStateMachine, PioStateMachineInstance, ShiftDirection, Sm0, Sm1, Sm2}; +use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachine, PioStateMachineInstance, ShiftDirection, Sm0, Sm1, Sm2}; use embassy_rp::pio_instr_util; use embassy_rp::relocate::RelocatedProgram; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/rp/src/bin/pio_dma.rs b/examples/rp/src/bin/pio_dma.rs index b19ef408..145e4a65 100644 --- a/examples/rp/src/bin/pio_dma.rs +++ b/examples/rp/src/bin/pio_dma.rs @@ -4,7 +4,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_rp::pio::{PioPeripherial, PioStateMachine, ShiftDirection}; +use embassy_rp::pio::{PioPeripheral, PioStateMachine, ShiftDirection}; use embassy_rp::relocate::RelocatedProgram; use embassy_rp::{pio_instr_util, Peripheral}; use {defmt_rtt as _, panic_probe as _}; From 7783e0ebb1a3870f3b180c87c1acd2807439f976 Mon Sep 17 00:00:00 2001 From: Caleb Jamison Date: Fri, 17 Feb 2023 07:43:19 -0500 Subject: [PATCH 79/95] Change timing window to match values found experimentally. --- embassy-lora/src/sx126x/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-lora/src/sx126x/mod.rs b/embassy-lora/src/sx126x/mod.rs index a9aeadfc..8559574c 100644 --- a/embassy-lora/src/sx126x/mod.rs +++ b/embassy-lora/src/sx126x/mod.rs @@ -55,10 +55,10 @@ where BUS: Error + Format + 'static, { fn get_rx_window_offset_ms(&self) -> i32 { - -3 + -50 } fn get_rx_window_duration_ms(&self) -> u32 { - 1003 + 1050 } } From a53f525f510de07e8c35d38ecc575cb8ea929dd9 Mon Sep 17 00:00:00 2001 From: chemicstry Date: Sat, 18 Feb 2023 01:35:35 +0200 Subject: [PATCH 80/95] stm32/sdmmc: Fix SDIOv1 writes --- embassy-stm32/src/dma/bdma.rs | 1 + embassy-stm32/src/dma/dma.rs | 23 +++++++++++++++++- embassy-stm32/src/dma/gpdma.rs | 10 +++++++- embassy-stm32/src/dma/mod.rs | 16 +++++++++++++ embassy-stm32/src/sdmmc/mod.rs | 40 ++++++++++++++++++++++++++++++- examples/stm32f4/src/bin/sdmmc.rs | 39 ++++++++++++++++++++++++++++-- 6 files changed, 124 insertions(+), 5 deletions(-) diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs index 6160b9f4..5a7a408b 100644 --- a/embassy-stm32/src/dma/bdma.rs +++ b/embassy-stm32/src/dma/bdma.rs @@ -192,6 +192,7 @@ mod low_level_api { options.flow_ctrl == crate::dma::FlowControl::Dma, "Peripheral flow control not supported" ); + assert!(options.fifo_threshold.is_none(), "FIFO mode not supported"); let ch = dma.ch(channel_number as _); diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index fec60f70..385a833f 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -4,7 +4,7 @@ use core::task::Waker; use embassy_cortex_m::interrupt::Priority; use embassy_sync::waitqueue::AtomicWaker; -use super::{Burst, FlowControl, Request, TransferOptions, Word, WordSize}; +use super::{Burst, FifoThreshold, FlowControl, Request, TransferOptions, Word, WordSize}; use crate::_generated::DMA_CHANNEL_COUNT; use crate::interrupt::{Interrupt, InterruptExt}; use crate::pac::dma::{regs, vals}; @@ -40,6 +40,17 @@ impl From for vals::Pfctrl { } } +impl From for vals::Fth { + fn from(value: FifoThreshold) -> Self { + match value { + FifoThreshold::Quarter => vals::Fth::QUARTER, + FifoThreshold::Half => vals::Fth::HALF, + FifoThreshold::ThreeQuarters => vals::Fth::THREEQUARTERS, + FifoThreshold::Full => vals::Fth::FULL, + } + } +} + struct ChannelState { waker: AtomicWaker, } @@ -236,6 +247,16 @@ mod low_level_api { ch.par().write_value(peri_addr as u32); ch.m0ar().write_value(mem_addr as u32); ch.ndtr().write_value(regs::Ndtr(mem_len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(vals::Dmdis::ENABLED); + } + }); ch.cr().write(|w| { w.set_dir(dir); w.set_msize(data_size); diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index d252cef3..442fee48 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -176,8 +176,16 @@ mod low_level_api { mem_len: usize, incr_mem: bool, data_size: WordSize, - _options: TransferOptions, + options: TransferOptions, ) { + assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported"); + assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported"); + assert!( + options.flow_ctrl == crate::dma::FlowControl::Dma, + "Peripheral flow control not supported" + ); + assert!(options.fifo_threshold.is_none(), "FIFO mode not supported"); + // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index b51e0d40..f5a82fb7 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -186,6 +186,19 @@ pub enum FlowControl { Peripheral, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FifoThreshold { + /// 1/4 full FIFO + Quarter, + /// 1/2 full FIFO + Half, + /// 3/4 full FIFO + ThreeQuarters, + /// Full FIFO + Full, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TransferOptions { @@ -195,6 +208,8 @@ pub struct TransferOptions { pub mburst: Burst, /// Flow control configuration pub flow_ctrl: FlowControl, + /// FIFO threshold for DMA FIFO mode. If none, direct mode is used. + pub fifo_threshold: Option, } impl Default for TransferOptions { @@ -203,6 +218,7 @@ impl Default for TransferOptions { pburst: Burst::Single, mburst: Burst::Single, flow_ctrl: FlowControl::Dma, + fifo_threshold: None, } } } diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 3f07e0c0..3c99a0b6 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -2,6 +2,7 @@ use core::default::Default; use core::future::poll_fn; +use core::ops::{Deref, DerefMut}; use core::task::Poll; use embassy_hal_common::drop::OnDrop; @@ -40,7 +41,23 @@ impl Default for Signalling { } #[repr(align(4))] -pub struct DataBlock([u8; 512]); +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataBlock(pub [u8; 512]); + +impl Deref for DataBlock { + type Target = [u8; 512]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DataBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} /// Errors #[non_exhaustive] @@ -570,6 +587,12 @@ impl SdmmcInner { regs.clkcr().write(|w| { w.set_pwrsav(false); w.set_negedge(false); + + // Hardware flow control is broken on SDIOv1 and causes clock glitches, which result in CRC errors. + // See chip erratas for more details. + #[cfg(sdmmc_v1)] + w.set_hwfc_en(false); + #[cfg(sdmmc_v2)] w.set_hwfc_en(true); #[cfg(sdmmc_v1)] @@ -807,10 +830,16 @@ impl SdmmcInner { let regs = self.0; let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); + // sdmmc_v1 uses different cmd/dma order than v2, but only for writes + #[cfg(sdmmc_v1)] + self.cmd(Cmd::write_single_block(address), true)?; + unsafe { self.prepare_datapath_write(buffer as *const [u32; 128], 512, 9, data_transfer_timeout, dma); self.data_interrupts(true); } + + #[cfg(sdmmc_v2)] self.cmd(Cmd::write_single_block(address), true)?; let res = poll_fn(|cx| { @@ -922,7 +951,9 @@ impl SdmmcInner { let request = dma.request(); dma.start_read(request, regs.fifor().ptr() as *const u32, buffer, crate::dma::TransferOptions { pburst: crate::dma::Burst::Incr4, + mburst: crate::dma::Burst::Incr4, flow_ctrl: crate::dma::FlowControl::Peripheral, + fifo_threshold: Some(crate::dma::FifoThreshold::Full), ..Default::default() }); } else if #[cfg(sdmmc_v2)] { @@ -970,7 +1001,9 @@ impl SdmmcInner { let request = dma.request(); dma.start_write(request, buffer, regs.fifor().ptr() as *mut u32, crate::dma::TransferOptions { pburst: crate::dma::Burst::Incr4, + mburst: crate::dma::Burst::Incr4, flow_ctrl: crate::dma::FlowControl::Peripheral, + fifo_threshold: Some(crate::dma::FifoThreshold::Full), ..Default::default() }); } else if #[cfg(sdmmc_v2)] { @@ -982,6 +1015,11 @@ impl SdmmcInner { regs.dctrl().modify(|w| { w.set_dblocksize(block_size); w.set_dtdir(false); + #[cfg(sdmmc_v1)] + { + w.set_dmaen(true); + w.set_dten(true); + } }); } diff --git a/examples/stm32f4/src/bin/sdmmc.rs b/examples/stm32f4/src/bin/sdmmc.rs index 0edd8a61..b57e955f 100644 --- a/examples/stm32f4/src/bin/sdmmc.rs +++ b/examples/stm32f4/src/bin/sdmmc.rs @@ -4,11 +4,15 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::sdmmc::Sdmmc; +use embassy_stm32::sdmmc::{DataBlock, Sdmmc}; use embassy_stm32::time::mhz; use embassy_stm32::{interrupt, Config}; use {defmt_rtt as _, panic_probe as _}; +/// This is a safeguard to not overwrite any data on the SD card. +/// If you don't care about SD card contents, set this to `true` to test writes. +const ALLOW_WRITES: bool = false; + #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { let mut config = Config::default(); @@ -34,11 +38,42 @@ async fn main(_spawner: Spawner) -> ! { // Should print 400kHz for initialization info!("Configured clock: {}", sdmmc.clock().0); - unwrap!(sdmmc.init_card(mhz(25)).await); + unwrap!(sdmmc.init_card(mhz(24)).await); let card = unwrap!(sdmmc.card()); info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", sdmmc.clock()); + + // Arbitrary block index + let block_idx = 16; + + // SDMMC uses `DataBlock` instead of `&[u8]` to ensure 4 byte alignment required by the hardware. + let mut block = DataBlock([0u8; 512]); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); + + if !ALLOW_WRITES { + info!("Writing is disabled."); + loop {} + } + + info!("Filling block with 0x55"); + block.fill(0x55); + sdmmc.write_block(block_idx, &block).await.unwrap(); + info!("Write done"); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); + + info!("Filling block with 0xAA"); + block.fill(0xAA); + sdmmc.write_block(block_idx, &block).await.unwrap(); + info!("Write done"); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); loop {} } From 272982ee5417ea6bdd5873e36717ba9ae9a59ee4 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 19 Feb 2023 23:00:16 +0100 Subject: [PATCH 81/95] examples/stm32wb: fix linker script. cortex-m-rt 0.7.2 now enforces the stack is 8-byte aligned. Stack is placed at `ORIGIN(RAM) + LENGTH(RAM)` by default, which wasn't 8-byte-aligned. See https://github.com/rust-embedded/cortex-m/discussions/469 ST trims 8 bytes from start of RAM, and uses the whole 192kb, so let's just copy that: https://github.com/STMicroelectronics/STM32CubeWB/blob/bceb1dae09f912eae47fba059251d499ba32f4ca/Drivers/CMSIS/Device/ST/STM32WBxx/Source/Templates/gcc/linker/stm32wb55xx_flash_cm4.ld#L48 --- examples/stm32wb/memory.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/stm32wb/memory.x b/examples/stm32wb/memory.x index 2b4dcce3..ae14e5a1 100644 --- a/examples/stm32wb/memory.x +++ b/examples/stm32wb/memory.x @@ -6,7 +6,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K - RAM (xrw) : ORIGIN = 0x20000004, LENGTH = 191K + RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K } From 13328c58d385ef57db74d902b1056c175a2ab367 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 20 Feb 2023 01:01:01 +0100 Subject: [PATCH 82/95] examples/stm32wb: do not reserve words at start of RAM. They're used to communicate from the app to ST's OTA bootloader. See AN5247. This bootloader is optional, must be flashed by the user, and requires changing the FLASH start address as well, so the current memory regions still require modifications to use it. Therefore there's no point in reserving these words. Thanks @adamgreig for investigating the purpose. --- examples/stm32wb/memory.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/stm32wb/memory.x b/examples/stm32wb/memory.x index ae14e5a1..0e48c916 100644 --- a/examples/stm32wb/memory.x +++ b/examples/stm32wb/memory.x @@ -6,7 +6,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K - RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K } From 3f88bf6f9b998e209c6bfe860930fc82516f3f9c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 23 Jan 2023 01:48:35 +0100 Subject: [PATCH 83/95] nrf: add support for UICR configuration. - APPROTECT enable/disable. Notably this fixes issues with nrf52-rev3 and nrf53 from locking itself at reset. - Use NFC pins as GPIO. - Use RESET pin as GPIO. NFC and RESET pins singletons are made available only when usable as GPIO, for compile-time checking. --- ci.sh | 4 +- embassy-nrf/Cargo.toml | 35 ++++-- embassy-nrf/src/chips/nrf52805.rs | 4 + embassy-nrf/src/chips/nrf52810.rs | 4 + embassy-nrf/src/chips/nrf52811.rs | 4 + embassy-nrf/src/chips/nrf52820.rs | 4 + embassy-nrf/src/chips/nrf52832.rs | 8 ++ embassy-nrf/src/chips/nrf52833.rs | 8 ++ embassy-nrf/src/chips/nrf52840.rs | 8 ++ embassy-nrf/src/chips/nrf5340_app.rs | 4 + embassy-nrf/src/lib.rs | 156 +++++++++++++++++++++++++++ embassy-nrf/src/nvmc.rs | 12 +-- 12 files changed, 232 insertions(+), 19 deletions(-) diff --git a/ci.sh b/ci.sh index 4199f91d..417937d0 100755 --- a/ci.sh +++ b/ci.sh @@ -46,8 +46,8 @@ cargo batch \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1,reset-pin-as-gpio \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits,nfc-pins-as-gpio \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \ diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 6b06d5d0..c31ce199 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -34,22 +34,30 @@ unstable-pac = [] # Implement embedded-hal-async traits if `nightly` is set as well. unstable-traits = ["embedded-hal-1"] -nrf52805 = ["nrf52805-pac", "_ppi"] -nrf52810 = ["nrf52810-pac", "_ppi"] -nrf52811 = ["nrf52811-pac", "_ppi"] -nrf52820 = ["nrf52820-pac", "_ppi"] -nrf52832 = ["nrf52832-pac", "_ppi"] -nrf52833 = ["nrf52833-pac", "_ppi", "_gpio-p1"] -nrf52840 = ["nrf52840-pac", "_ppi", "_gpio-p1"] -nrf5340-app-s = ["_nrf5340-app"] -nrf5340-app-ns = ["_nrf5340-app"] +nrf52805 = ["nrf52805-pac", "_nrf52"] +nrf52810 = ["nrf52810-pac", "_nrf52"] +nrf52811 = ["nrf52811-pac", "_nrf52"] +nrf52820 = ["nrf52820-pac", "_nrf52"] +nrf52832 = ["nrf52832-pac", "_nrf52"] +nrf52833 = ["nrf52833-pac", "_nrf52", "_gpio-p1"] +nrf52840 = ["nrf52840-pac", "_nrf52", "_gpio-p1"] +nrf5340-app-s = ["_nrf5340-app", "_s"] +nrf5340-app-ns = ["_nrf5340-app", "_ns"] nrf5340-net = ["_nrf5340-net"] -nrf9160-s = ["_nrf9160"] -nrf9160-ns = ["_nrf9160"] +nrf9160-s = ["_nrf9160", "_s"] +nrf9160-ns = ["_nrf9160", "_ns"] gpiote = [] time-driver-rtc1 = ["_time-driver"] +# Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53) +nfc-pins-as-gpio = [] + +# Allow using the RST pin as a regular GPIO pin. +# nrf52805, nrf52810, nrf52811, nrf52832: P0_21 +# nrf52820, nrf52833, nrf52840: P0_18 +reset-pin-as-gpio = [] + # 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. @@ -57,9 +65,14 @@ _nrf5340-app = ["_nrf5340", "nrf5340-app-pac"] _nrf5340-net = ["_nrf5340", "nrf5340-net-pac"] _nrf5340 = ["_gpio-p1", "_dppi"] _nrf9160 = ["nrf9160-pac", "_dppi"] +_nrf52 = ["_ppi"] _time-driver = ["dep:embassy-time", "embassy-time?/tick-hz-32_768"] +# trustzone state. +_s = [] +_ns = [] + _ppi = [] _dppi = [] _gpio-p1 = [] diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index bf4019c1..3c74a2a6 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256; pub const FLASH_SIZE: usize = 192 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -108,6 +110,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -162,6 +165,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 6c28a3be..6b5c134b 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256; pub const FLASH_SIZE: usize = 192 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -111,6 +113,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -170,6 +173,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index e7214cf5..c5de9a44 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256; pub const FLASH_SIZE: usize = 192 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -111,6 +113,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -172,6 +175,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index 21d1d16c..81b07f32 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512; pub const FLASH_SIZE: usize = 256 * 1024; +pub const RESET_PIN: u32 = 18; + embassy_hal_common::peripherals! { // USB USBD, @@ -106,6 +108,7 @@ embassy_hal_common::peripherals! { P0_15, P0_16, P0_17, + #[cfg(feature="reset-pin-as-gpio")] P0_18, P0_19, P0_20, @@ -168,6 +171,7 @@ impl_pin!(P0_14, 0, 14); impl_pin!(P0_15, 0, 15); impl_pin!(P0_16, 0, 16); impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 152dad4e..c2b23fc5 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -10,6 +10,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 255; // nrf52832xxAB = 256kb pub const FLASH_SIZE: usize = 512 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -109,7 +111,9 @@ embassy_hal_common::peripherals! { P0_06, P0_07, P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] P0_10, P0_11, P0_12, @@ -121,6 +125,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -178,7 +183,9 @@ impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); impl_pin!(P0_07, 0, 7); impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_10, 0, 10); impl_pin!(P0_11, 0, 11); impl_pin!(P0_12, 0, 12); @@ -190,6 +197,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index a99ca634..95f71ade 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512; pub const FLASH_SIZE: usize = 512 * 1024; +pub const RESET_PIN: u32 = 18; + embassy_hal_common::peripherals! { // USB USBD, @@ -111,7 +113,9 @@ embassy_hal_common::peripherals! { P0_06, P0_07, P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] P0_10, P0_11, P0_12, @@ -120,6 +124,7 @@ embassy_hal_common::peripherals! { P0_15, P0_16, P0_17, + #[cfg(feature="reset-pin-as-gpio")] P0_18, P0_19, P0_20, @@ -207,7 +212,9 @@ impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); impl_pin!(P0_07, 0, 7); impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_10, 0, 10); impl_pin!(P0_11, 0, 11); impl_pin!(P0_12, 0, 12); @@ -216,6 +223,7 @@ impl_pin!(P0_14, 0, 14); impl_pin!(P0_15, 0, 15); impl_pin!(P0_16, 0, 16); impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 4f7463be..5e7479e8 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512; pub const FLASH_SIZE: usize = 1024 * 1024; +pub const RESET_PIN: u32 = 18; + embassy_hal_common::peripherals! { // USB USBD, @@ -117,7 +119,9 @@ embassy_hal_common::peripherals! { P0_06, P0_07, P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] P0_10, P0_11, P0_12, @@ -126,6 +130,7 @@ embassy_hal_common::peripherals! { P0_15, P0_16, P0_17, + #[cfg(feature="reset-pin-as-gpio")] P0_18, P0_19, P0_20, @@ -212,7 +217,9 @@ impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); impl_pin!(P0_07, 0, 7); impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_10, 0, 10); impl_pin!(P0_11, 0, 11); impl_pin!(P0_12, 0, 12); @@ -221,6 +228,7 @@ impl_pin!(P0_14, 0, 14); impl_pin!(P0_15, 0, 15); impl_pin!(P0_16, 0, 16); impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index c600fcbf..2b8b55e7 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -304,7 +304,9 @@ embassy_hal_common::peripherals! { // GPIO port 0 P0_00, P0_01, + #[cfg(feature = "nfc-pins-as-gpio")] P0_02, + #[cfg(feature = "nfc-pins-as-gpio")] P0_03, P0_04, P0_05, @@ -393,7 +395,9 @@ impl_timer!(TIMER2, TIMER2, TIMER2); impl_pin!(P0_00, 0, 0); impl_pin!(P0_01, 0, 1); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_02, 0, 2); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_03, 0, 3); impl_pin!(P0_04, 0, 4); impl_pin!(P0_05, 0, 5); diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 20e70a24..0fa1c0f6 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -24,6 +24,12 @@ )))] compile_error!("No chip feature activated. You must activate exactly one of the following features: nrf52810, nrf52811, nrf52832, nrf52833, nrf52840"); +#[cfg(all(feature = "reset-pin-as-gpio", not(feature = "_nrf52")))] +compile_error!("feature `reset-pin-as-gpio` is only valid for nRF52 series chips."); + +#[cfg(all(feature = "nfc-pins-as-gpio", not(any(feature = "_nrf52", feature = "_nrf5340-app"))))] +compile_error!("feature `nfc-pins-as-gpio` is only valid for nRF52, or nRF53's application core."); + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; pub(crate) mod util; @@ -139,6 +145,19 @@ pub mod config { ExternalFullSwing, } + /// SWD access port protection setting. + #[non_exhaustive] + pub enum Debug { + /// Debugging is allowed (APPROTECT is disabled). Default. + Allowed, + /// Debugging is not allowed (APPROTECT is enabled). + Disallowed, + /// APPROTECT is not configured (neither to enable it or disable it). + /// This can be useful if you're already doing it by other means and + /// you don't want embassy-nrf to touch UICR. + NotConfigured, + } + /// Configuration for peripherals. Default configuration should work on any nRF chip. #[non_exhaustive] pub struct Config { @@ -152,6 +171,8 @@ pub mod config { /// Time driver interrupt priority. Should be lower priority than softdevice if used. #[cfg(feature = "_time-driver")] pub time_interrupt_priority: crate::interrupt::Priority, + /// Enable or disable the debug port. + pub debug: Debug, } impl Default for Config { @@ -166,17 +187,152 @@ pub mod config { gpiote_interrupt_priority: crate::interrupt::Priority::P0, #[cfg(feature = "_time-driver")] time_interrupt_priority: crate::interrupt::Priority::P0, + + // In NS mode, default to NotConfigured, assuming the S firmware will do it. + #[cfg(feature = "_ns")] + debug: Debug::NotConfigured, + #[cfg(not(feature = "_ns"))] + debug: Debug::Allowed, } } } } +#[cfg(feature = "_nrf9160")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF802C as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; +} + +#[cfg(feature = "_nrf5340-app")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF801C as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x00FF8028 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf5340-net")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x01FF8000 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf52")] +#[allow(unused)] +mod consts { + pub const UICR_PSELRESET1: *mut u32 = 0x10001200 as *mut u32; + pub const UICR_PSELRESET2: *mut u32 = 0x10001204 as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x1000120C as *mut u32; + pub const UICR_APPROTECT: *mut u32 = 0x10001208 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x0000_005a; +} + +unsafe fn uicr_write(address: *mut u32, value: u32) -> bool { + let curr_val = address.read_volatile(); + if curr_val == value { + return false; + } + + // Writing to UICR can only change `1` bits to `0` bits. + // If this write would change `0` bits to `1` bits, we can't do it. + // It is only possible to do when erasing UICR, which is forbidden if + // APPROTECT is enabled. + if (!curr_val) & value != 0 { + panic!("Cannot write UICR address={:08x} value={:08x}", address as u32, value) + } + + let nvmc = &*pac::NVMC::ptr(); + nvmc.config.write(|w| w.wen().wen()); + while nvmc.ready.read().ready().is_busy() {} + address.write_volatile(value); + while nvmc.ready.read().ready().is_busy() {} + nvmc.config.reset(); + while nvmc.ready.read().ready().is_busy() {} + + true +} + /// Initialize peripherals with the provided configuration. This should only be called once at startup. pub fn init(config: config::Config) -> Peripherals { // Do this first, so that it panics if user is calling `init` a second time // before doing anything important. let peripherals = Peripherals::take(); + let mut needs_reset = false; + + // Setup debug protection. + match config.debug { + config::Debug::Allowed => { + #[cfg(feature = "_nrf52")] + unsafe { + let variant = (0x1000_0104 as *mut u32).read_volatile(); + // Get the letter for the build code (b'A' .. b'F') + let build_code = (variant >> 8) as u8; + + if build_code >= b'F' { + // Chips with build code F and higher (revision 3 and higher) have an + // improved APPROTECT ("hardware and software controlled access port protection") + // which needs explicit action by the firmware to keep it unlocked + + // UICR.APPROTECT = SwDisabled + needs_reset |= uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + // APPROTECT.DISABLE = SwDisabled + (0x4000_0558 as *mut u32).write_volatile(consts::APPROTECT_DISABLED); + } else { + // nothing to do on older chips, debug is allowed by default. + } + } + + #[cfg(feature = "_nrf5340")] + unsafe { + let p = &*pac::CTRLAP::ptr(); + + needs_reset |= uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + p.approtect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); + + #[cfg(feature = "_nrf5340-app")] + { + needs_reset |= uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); + p.secureapprotect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); + } + } + + // nothing to do on the nrf9160, debug is allowed by default. + } + config::Debug::Disallowed => unsafe { + // UICR.APPROTECT = Enabled + needs_reset |= uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); + #[cfg(any(feature = "_nrf5340-app", feature = "_nrf9160"))] + { + needs_reset |= uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + } + }, + config::Debug::NotConfigured => {} + } + + #[cfg(all(feature = "_nrf52", not(feature = "reset-pin-as-gpio")))] + unsafe { + needs_reset |= uicr_write(consts::UICR_PSELRESET1, chip::RESET_PIN); + needs_reset |= uicr_write(consts::UICR_PSELRESET2, chip::RESET_PIN); + } + + #[cfg(all(any(feature = "_nrf52", feature = "_nrf5340-app"), feature = "nfc-pins-as-gpio"))] + unsafe { + needs_reset |= uicr_write(consts::UICR_NFCPINS, 0); + } + + if needs_reset { + cortex_m::peripheral::SCB::sys_reset(); + } + let r = unsafe { &*pac::CLOCK::ptr() }; // Start HFCLK. diff --git a/embassy-nrf/src/nvmc.rs b/embassy-nrf/src/nvmc.rs index c1ffa31a..6f48853d 100644 --- a/embassy-nrf/src/nvmc.rs +++ b/embassy-nrf/src/nvmc.rs @@ -85,23 +85,23 @@ impl<'d> Nvmc<'d> { } fn enable_erase(&self) { - #[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] + #[cfg(not(feature = "_ns"))] Self::regs().config.write(|w| w.wen().een()); - #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + #[cfg(feature = "_ns")] Self::regs().configns.write(|w| w.wen().een()); } fn enable_read(&self) { - #[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] + #[cfg(not(feature = "_ns"))] Self::regs().config.write(|w| w.wen().ren()); - #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + #[cfg(feature = "_ns")] Self::regs().configns.write(|w| w.wen().ren()); } fn enable_write(&self) { - #[cfg(not(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns")))] + #[cfg(not(feature = "_ns"))] Self::regs().config.write(|w| w.wen().wen()); - #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + #[cfg(feature = "_ns")] Self::regs().configns.write(|w| w.wen().wen()); } } From 7fa478358ac117557af778d12d7812da5521fdfd Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 20 Feb 2023 01:29:12 +0100 Subject: [PATCH 84/95] nrf: warn if uicr configuration could not be written. If the user requests some configuration, but UICR is already programmed to something else, detect this and warn the user. We don't do it for the debug port settings, because if they are wrong then the user will simply not be able to read debug logs. --- embassy-nrf/src/lib.rs | 78 +++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 0fa1c0f6..a9683df4 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -235,10 +235,26 @@ mod consts { pub const APPROTECT_DISABLED: u32 = 0x0000_005a; } -unsafe fn uicr_write(address: *mut u32, value: u32) -> bool { +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum WriteResult { + /// Word was written successfully, needs reset. + Written, + /// Word was already set to the value we wanted to write, nothing was done. + Noop, + /// Word is already set to something else, we couldn't write the desired value. + Failed, +} + +unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { let curr_val = address.read_volatile(); if curr_val == value { - return false; + return WriteResult::Noop; + } + + // We can only change `1` bits to `0` bits. + if curr_val & value != value { + return WriteResult::Failed; } // Writing to UICR can only change `1` bits to `0` bits. @@ -257,7 +273,7 @@ unsafe fn uicr_write(address: *mut u32, value: u32) -> bool { nvmc.config.reset(); while nvmc.ready.read().ready().is_busy() {} - true + WriteResult::Written } /// Initialize peripherals with the provided configuration. This should only be called once at startup. @@ -283,7 +299,8 @@ pub fn init(config: config::Config) -> Peripherals { // which needs explicit action by the firmware to keep it unlocked // UICR.APPROTECT = SwDisabled - needs_reset |= uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; // APPROTECT.DISABLE = SwDisabled (0x4000_0558 as *mut u32).write_volatile(consts::APPROTECT_DISABLED); } else { @@ -295,12 +312,14 @@ pub fn init(config: config::Config) -> Peripherals { unsafe { let p = &*pac::CTRLAP::ptr(); - needs_reset |= uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; p.approtect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); #[cfg(feature = "_nrf5340-app")] { - needs_reset |= uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; p.secureapprotect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); } } @@ -309,24 +328,57 @@ pub fn init(config: config::Config) -> Peripherals { } config::Debug::Disallowed => unsafe { // UICR.APPROTECT = Enabled - needs_reset |= uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; #[cfg(any(feature = "_nrf5340-app", feature = "_nrf9160"))] { - needs_reset |= uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; } }, config::Debug::NotConfigured => {} } - #[cfg(all(feature = "_nrf52", not(feature = "reset-pin-as-gpio")))] + #[cfg(feature = "_nrf52")] unsafe { - needs_reset |= uicr_write(consts::UICR_PSELRESET1, chip::RESET_PIN); - needs_reset |= uicr_write(consts::UICR_PSELRESET2, chip::RESET_PIN); + let value = if cfg!(feature = "reset-pin-as-gpio") { + !0 + } else { + chip::RESET_PIN + }; + let res1 = uicr_write(consts::UICR_PSELRESET1, value); + let res2 = uicr_write(consts::UICR_PSELRESET2, value); + needs_reset |= res1 == WriteResult::Written || res2 == WriteResult::Written; + if res1 == WriteResult::Failed || res2 == WriteResult::Failed { + #[cfg(not(feature = "reset-pin-as-gpio"))] + warn!( + "You have requested enabling chip reset functionality on the reset pin, by not enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`." + ); + #[cfg(feature = "reset-pin-as-gpio")] + warn!( + "You have requested using the reset pin as GPIO, by enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`." + ); + } } - #[cfg(all(any(feature = "_nrf52", feature = "_nrf5340-app"), feature = "nfc-pins-as-gpio"))] + #[cfg(any(feature = "_nrf52", feature = "_nrf5340-app"))] unsafe { - needs_reset |= uicr_write(consts::UICR_NFCPINS, 0); + let value = if cfg!(feature = "nfc-pins-as-gpio") { 0 } else { !0 }; + let res = uicr_write(consts::UICR_NFCPINS, value); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + // with nfc-pins-as-gpio, this can never fail because we're writing all zero bits. + #[cfg(not(feature = "nfc-pins-as-gpio"))] + warn!( + "You have requested to use P0.09 and P0.10 pins for NFC, by not enabling the Cargo feature `nfc-pins-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs-cli erase` or `nrfjprog --eraseuicr`." + ); + } } if needs_reset { From ada3d5be7c9746819c9c0c73d3031a68ac20e6fe Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 21 Feb 2023 22:41:23 +0100 Subject: [PATCH 85/95] nrf: rename UARTETWISPIn -> SERIALn The UARTETWISPIn naming is quite horrible. With the nRF53, Nordic realized this and renamed the interrupts to SERIALn. Let's copy that for our peripheral names, in nrf53 and nrf91. --- embassy-nrf/src/chips/nrf5340_app.rs | 48 ++++++++++++++-------------- embassy-nrf/src/chips/nrf5340_net.rs | 18 +++++------ embassy-nrf/src/chips/nrf9160.rs | 48 ++++++++++++++-------------- examples/nrf5340/src/bin/uart.rs | 2 +- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index 2b8b55e7..2e1c7f38 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -231,10 +231,10 @@ embassy_hal_common::peripherals! { NVMC, // UARTE, TWI & SPI - UARTETWISPI0, - UARTETWISPI1, - UARTETWISPI2, - UARTETWISPI3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, // SAADC SAADC, @@ -359,30 +359,30 @@ embassy_hal_common::peripherals! { #[cfg(feature = "nightly")] impl_usb!(USBD, USBD, USBD); -impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); -impl_uarte!(UARTETWISPI1, UARTE1, SERIAL1); -impl_uarte!(UARTETWISPI2, UARTE2, SERIAL2); -impl_uarte!(UARTETWISPI3, UARTE3, SERIAL3); +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); -impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); -impl_spim!(UARTETWISPI1, SPIM1, SERIAL1); -impl_spim!(UARTETWISPI2, SPIM2, SERIAL2); -impl_spim!(UARTETWISPI3, SPIM3, SERIAL3); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); -impl_spis!(UARTETWISPI0, SPIS0, SERIAL0); -impl_spis!(UARTETWISPI1, SPIS1, SERIAL1); -impl_spis!(UARTETWISPI2, SPIS2, SERIAL2); -impl_spis!(UARTETWISPI3, SPIS3, SERIAL3); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); -impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); -impl_twim!(UARTETWISPI1, TWIM1, SERIAL1); -impl_twim!(UARTETWISPI2, TWIM2, SERIAL2); -impl_twim!(UARTETWISPI3, TWIM3, SERIAL3); +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); -impl_twis!(UARTETWISPI0, TWIS0, SERIAL0); -impl_twis!(UARTETWISPI1, TWIS1, SERIAL1); -impl_twis!(UARTETWISPI2, TWIS2, SERIAL2); -impl_twis!(UARTETWISPI3, TWIS3, SERIAL3); +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index a46583ec..d7ba6c16 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -119,10 +119,10 @@ embassy_hal_common::peripherals! { NVMC, // UARTE, TWI & SPI - UARTETWISPI0, - UARTETWISPI1, - UARTETWISPI2, - UARTETWISPI3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, // SAADC SAADC, @@ -242,11 +242,11 @@ embassy_hal_common::peripherals! { P1_15, } -impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); -impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); -impl_spis!(UARTETWISPI0, SPIS0, SERIAL0); -impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); -impl_twis!(UARTETWISPI0, TWIS0, SERIAL0); +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twis!(SERIAL0, TWIS0, SERIAL0); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); diff --git a/embassy-nrf/src/chips/nrf9160.rs b/embassy-nrf/src/chips/nrf9160.rs index e1509ddd..385bd133 100644 --- a/embassy-nrf/src/chips/nrf9160.rs +++ b/embassy-nrf/src/chips/nrf9160.rs @@ -179,10 +179,10 @@ embassy_hal_common::peripherals! { NVMC, // UARTE, TWI & SPI - UARTETWISPI0, - UARTETWISPI1, - UARTETWISPI2, - UARTETWISPI3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, // SAADC SAADC, @@ -271,30 +271,30 @@ embassy_hal_common::peripherals! { PDM, } -impl_uarte!(UARTETWISPI0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_uarte!(UARTETWISPI1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_uarte!(UARTETWISPI2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_uarte!(UARTETWISPI3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_uarte!(SERIAL0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_uarte!(SERIAL1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_uarte!(SERIAL2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_uarte!(SERIAL3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); -impl_spim!(UARTETWISPI0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_spim!(UARTETWISPI1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spim!(SERIAL0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_spim!(SERIAL1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_spim!(SERIAL2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_spim!(SERIAL3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); -impl_spis!(UARTETWISPI0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_spis!(UARTETWISPI1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_spis!(UARTETWISPI2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_spis!(UARTETWISPI3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spis!(SERIAL0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_spis!(SERIAL1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_spis!(SERIAL2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_spis!(SERIAL3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); -impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_twim!(SERIAL0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_twim!(SERIAL1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_twim!(SERIAL2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_twim!(SERIAL3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); -impl_twis!(UARTETWISPI0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_twis!(UARTETWISPI1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_twis!(UARTETWISPI2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_twis!(UARTETWISPI3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_twis!(SERIAL0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_twis!(SERIAL1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_twis!(SERIAL2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_twis!(SERIAL3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); diff --git a/examples/nrf5340/src/bin/uart.rs b/examples/nrf5340/src/bin/uart.rs index 0f2b7b1e..5f448c2b 100644 --- a/examples/nrf5340/src/bin/uart.rs +++ b/examples/nrf5340/src/bin/uart.rs @@ -15,7 +15,7 @@ async fn main(_spawner: Spawner) { config.baudrate = uarte::Baudrate::BAUD115200; let irq = interrupt::take!(SERIAL0); - let mut uart = uarte::Uarte::new(p.UARTETWISPI0, irq, p.P1_00, p.P1_01, config); + let mut uart = uarte::Uarte::new(p.SERIAL0, irq, p.P1_00, p.P1_01, config); info!("uarte initialized!"); From f1a4db44c4c3f591df378190a6604d4547a37c25 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Wed, 22 Feb 2023 13:57:40 +0100 Subject: [PATCH 86/95] Implement flush for TcpSocket --- embassy-net/src/tcp.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index d46bd4db..b9e494fc 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -254,10 +254,19 @@ impl<'d> TcpIo<'d> { .await } - #[allow(unused)] async fn flush(&mut self) -> Result<(), Error> { - poll_fn(move |_| { - Poll::Ready(Ok(())) // TODO: Is there a better implementation for this? + poll_fn(move |cx| { + self.with_mut(|s, _| { + // If there are outstanding send operations, register for wake up and wait + // smoltcp issues wake-ups when octets are dequeued from the send buffer + if s.send_queue() > 0 { + s.register_send_waker(cx.waker()); + Poll::Pending + // No outstanding sends, socket is flushed + } else { + Poll::Ready(Ok(())) + } + }) }) .await } From 035de6f3ff7bae31dce25b55e822f75b46ebe047 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Wed, 22 Feb 2023 14:45:17 +0100 Subject: [PATCH 87/95] embassy-net: add flush to TcpSocket and TcpWriter as an inherent method --- embassy-net/src/tcp.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index b9e494fc..c3d8764b 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -63,6 +63,10 @@ impl<'a> TcpWriter<'a> { pub async fn write(&mut self, buf: &[u8]) -> Result { self.io.write(buf).await } + + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } } impl<'a> TcpSocket<'a> { @@ -146,6 +150,10 @@ impl<'a> TcpSocket<'a> { self.io.write(buf).await } + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } + pub fn set_timeout(&mut self, duration: Option) { self.io.with_mut(|s, _| s.set_timeout(duration)) } From 4e884ee2d2f0c3f4a46f1bc539a12e9fdce173e2 Mon Sep 17 00:00:00 2001 From: Patrick Oppenlander Date: Thu, 23 Feb 2023 10:09:09 +1100 Subject: [PATCH 88/95] stm32/dma: fix spurious transfer complete interrupts DMA interrupts must be acknowledged by writing to the DMA_{L,H}IFCR register. Writing to the CR register is unnecessary as the channel (EN bit) is disabled by hardware on completion of the transfer. --- embassy-stm32/src/dma/dma.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index fec60f70..8966214e 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -411,12 +411,8 @@ mod low_level_api { } if isr.tcif(channel_num % 4) && cr.read().tcie() { - if cr.read().dbm() == vals::Dbm::DISABLED { - cr.write(|_| ()); // Disable channel with the default value. - } else { - // for double buffered mode, clear TCIF flag but do not stop the transfer - dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); - } + /* acknowledge transfer complete interrupt */ + dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); STATE.channels[state_index].waker.wake(); } } From 42462681bd604750dfe8fa709453edf43c25b09d Mon Sep 17 00:00:00 2001 From: chemicstry Date: Thu, 23 Feb 2023 16:57:21 +0200 Subject: [PATCH 89/95] stm32/sdmmc: Implement proper clock configuration --- embassy-stm32/src/sdmmc/mod.rs | 87 +++++++++++++++++++++++++------ examples/stm32f4/src/bin/sdmmc.rs | 3 +- examples/stm32f7/src/bin/sdmmc.rs | 1 + 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 3c99a0b6..0bcd42bc 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -140,10 +140,21 @@ cfg_if::cfg_if! { /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to /// `sdmmc_ck` in Hertz. /// - /// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register - /// value and `clk_f` is the resulting new clock frequency. - fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u8, Hertz), Error> { - let clk_div = match ker_ck.0 / sdmmc_ck { + /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), + /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. + fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> { + // sdmmc_v1 maximum clock is 50 MHz + if sdmmc_ck > 50_000_000 { + return Err(Error::BadClock); + } + + // bypass divisor + if ker_ck.0 <= sdmmc_ck { + return Ok((true, 0, ker_ck)); + } + + // `ker_ck / sdmmc_ck` rounded up + let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { 0 | 1 => Ok(0), x @ 2..=258 => { Ok((x - 2) as u8) @@ -153,22 +164,24 @@ cfg_if::cfg_if! { // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); - Ok((clk_div, clk_f)) + Ok((false, clk_div, clk_f)) } } else if #[cfg(sdmmc_v2)] { /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to /// `sdmmc_ck` in Hertz. /// - /// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register - /// value and `clk_f` is the resulting new clock frequency. - fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u16, Hertz), Error> { + /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), + /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. + fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> { + // `ker_ck / sdmmc_ck` rounded up match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { - 0 | 1 => Ok((0, ker_ck)), + 0 | 1 => Ok((false, 0, ker_ck)), x @ 2..=2046 => { + // SDMMC_CK frequency = SDMMCCLK / [CLKDIV + 2] let clk_div = ((x + 1) / 2) as u16; let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); - Ok((clk_div, clk)) + Ok((false, clk_div, clk)) } _ => Err(Error::BadClock), } @@ -478,7 +491,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { bus_width, &mut self.card, &mut self.signalling, - T::frequency(), + Self::kernel_clock(), &mut self.clock, T::state(), self.config.data_transfer_timeout, @@ -550,6 +563,44 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { regs.data_interrupts(false); state.wake(); } + + /// Returns kernel clock (SDIOCLK) for the SD-card facing domain + fn kernel_clock() -> Hertz { + cfg_if::cfg_if! { + // TODO, these could not be implemented, because required clocks are not exposed in RCC: + // - H7 uses pll1_q_ck or pll2_r_ck depending on SDMMCSEL + // - L1 uses pll48 + // - L4 uses clk48(pll48) + // - L4+, L5, U5 uses clk48(pll48) or PLLSAI3CLK(PLLP) depending on SDMMCSEL + if #[cfg(stm32f1)] { + // F1 uses AHB1(HCLK), which is correct in PAC + T::frequency() + } else if #[cfg(any(stm32f2, stm32f4))] { + // F2, F4 always use pll48 + critical_section::with(|_| unsafe { + crate::rcc::get_freqs().pll48 + }).expect("PLL48 is required for SDIO") + } else if #[cfg(stm32f7)] { + critical_section::with(|_| unsafe { + use core::any::TypeId; + let sdmmcsel = if TypeId::of::() == TypeId::of::() { + crate::pac::RCC.dckcfgr2().read().sdmmc1sel() + } else { + crate::pac::RCC.dckcfgr2().read().sdmmc2sel() + }; + + if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK { + crate::rcc::get_freqs().sys + } else { + crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC") + } + }) + } else { + // Use default peripheral clock and hope it works + T::frequency() + } + } + } } impl<'d, T: Instance, Dma> Drop for Sdmmc<'d, T, Dma> { @@ -625,7 +676,7 @@ impl SdmmcInner { unsafe { // While the SD/SDIO card or eMMC is in identification mode, // the SDMMC_CK frequency must be no more than 400 kHz. - let (clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); + let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); *clock = init_clock; // CPSMACT and DPSMACT must be 0 to set WIDBUS @@ -634,6 +685,8 @@ impl SdmmcInner { regs.clkcr().modify(|w| { w.set_widbus(0); w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); }); regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); @@ -1052,7 +1105,8 @@ impl SdmmcInner { _ => panic!("Invalid Bus Width"), }; - let (clkdiv, new_clock) = clk_div(ker_ck, freq)?; + let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?; + // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 // Section 55.5.8 let sdmmc_bus_bandwidth = new_clock.0 * width_u32; @@ -1063,7 +1117,11 @@ impl SdmmcInner { unsafe { // CPSMACT and DPSMACT must be 0 to set CLKDIV self.wait_idle(); - regs.clkcr().modify(|w| w.set_clkdiv(clkdiv)); + regs.clkcr().modify(|w| { + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); } Ok(()) @@ -1152,7 +1210,6 @@ impl SdmmcInner { } /// Query the card status (CMD13, returns R1) - /// fn read_status(&self, card: &Card) -> Result { let regs = self.0; let rca = card.rca; diff --git a/examples/stm32f4/src/bin/sdmmc.rs b/examples/stm32f4/src/bin/sdmmc.rs index b57e955f..1d0e60cb 100644 --- a/examples/stm32f4/src/bin/sdmmc.rs +++ b/examples/stm32f4/src/bin/sdmmc.rs @@ -17,6 +17,7 @@ const ALLOW_WRITES: bool = false; async fn main(_spawner: Spawner) -> ! { let mut config = Config::default(); config.rcc.sys_ck = Some(mhz(48)); + config.rcc.pll48 = true; let p = embassy_stm32::init(config); info!("Hello World!"); @@ -38,7 +39,7 @@ async fn main(_spawner: Spawner) -> ! { // Should print 400kHz for initialization info!("Configured clock: {}", sdmmc.clock().0); - unwrap!(sdmmc.init_card(mhz(24)).await); + unwrap!(sdmmc.init_card(mhz(48)).await); let card = unwrap!(sdmmc.card()); diff --git a/examples/stm32f7/src/bin/sdmmc.rs b/examples/stm32f7/src/bin/sdmmc.rs index 3bf427ec..cf8128e2 100644 --- a/examples/stm32f7/src/bin/sdmmc.rs +++ b/examples/stm32f7/src/bin/sdmmc.rs @@ -13,6 +13,7 @@ use {defmt_rtt as _, panic_probe as _}; async fn main(_spawner: Spawner) -> ! { let mut config = Config::default(); config.rcc.sys_ck = Some(mhz(200)); + config.rcc.pll48 = true; let p = embassy_stm32::init(config); info!("Hello World!"); From 896764bb8562b483ceecb39db2827061fc90d598 Mon Sep 17 00:00:00 2001 From: chemicstry Date: Thu, 23 Feb 2023 17:38:52 +0200 Subject: [PATCH 90/95] stm32/sdmmc: Refactor TypeId into a macro --- embassy-stm32/src/sdmmc/mod.rs | 100 ++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 0bcd42bc..2d91286f 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -491,7 +491,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { bus_width, &mut self.card, &mut self.signalling, - Self::kernel_clock(), + T::kernel_clk(), &mut self.clock, T::state(), self.config.data_transfer_timeout, @@ -563,44 +563,6 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { regs.data_interrupts(false); state.wake(); } - - /// Returns kernel clock (SDIOCLK) for the SD-card facing domain - fn kernel_clock() -> Hertz { - cfg_if::cfg_if! { - // TODO, these could not be implemented, because required clocks are not exposed in RCC: - // - H7 uses pll1_q_ck or pll2_r_ck depending on SDMMCSEL - // - L1 uses pll48 - // - L4 uses clk48(pll48) - // - L4+, L5, U5 uses clk48(pll48) or PLLSAI3CLK(PLLP) depending on SDMMCSEL - if #[cfg(stm32f1)] { - // F1 uses AHB1(HCLK), which is correct in PAC - T::frequency() - } else if #[cfg(any(stm32f2, stm32f4))] { - // F2, F4 always use pll48 - critical_section::with(|_| unsafe { - crate::rcc::get_freqs().pll48 - }).expect("PLL48 is required for SDIO") - } else if #[cfg(stm32f7)] { - critical_section::with(|_| unsafe { - use core::any::TypeId; - let sdmmcsel = if TypeId::of::() == TypeId::of::() { - crate::pac::RCC.dckcfgr2().read().sdmmc1sel() - } else { - crate::pac::RCC.dckcfgr2().read().sdmmc2sel() - }; - - if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK { - crate::rcc::get_freqs().sys - } else { - crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC") - } - }) - } else { - // Use default peripheral clock and hope it works - T::frequency() - } - } - } } impl<'d, T: Instance, Dma> Drop for Sdmmc<'d, T, Dma> { @@ -1580,6 +1542,7 @@ pub(crate) mod sealed { fn inner() -> SdmmcInner; fn state() -> &'static AtomicWaker; + fn kernel_clk() -> Hertz; } pub trait Pins {} @@ -1607,6 +1570,61 @@ cfg_if::cfg_if! { } } +cfg_if::cfg_if! { + // TODO, these could not be implemented, because required clocks are not exposed in RCC: + // - H7 uses pll1_q_ck or pll2_r_ck depending on SDMMCSEL + // - L1 uses pll48 + // - L4 uses clk48(pll48) + // - L4+, L5, U5 uses clk48(pll48) or PLLSAI3CLK(PLLP) depending on SDMMCSEL + if #[cfg(stm32f1)] { + // F1 uses AHB1(HCLK), which is correct in PAC + macro_rules! kernel_clk { + ($inst:ident) => { + peripherals::$inst::frequency() + } + } + } else if #[cfg(any(stm32f2, stm32f4))] { + // F2, F4 always use pll48 + macro_rules! kernel_clk { + ($inst:ident) => { + critical_section::with(|_| unsafe { + crate::rcc::get_freqs().pll48 + }).expect("PLL48 is required for SDIO") + } + } + } else if #[cfg(stm32f7)] { + macro_rules! kernel_clk { + (SDMMC1) => { + critical_section::with(|_| unsafe { + let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc1sel(); + if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK { + crate::rcc::get_freqs().sys + } else { + crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC") + } + }) + }; + (SDMMC2) => { + critical_section::with(|_| unsafe { + let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc2sel(); + if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK { + crate::rcc::get_freqs().sys + } else { + crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC") + } + }) + }; + } + } else { + // Use default peripheral clock and hope it works + macro_rules! kernel_clk { + ($inst:ident) => { + peripherals::$inst::frequency() + } + } + } +} + foreach_peripheral!( (sdmmc, $inst:ident) => { impl sealed::Instance for peripherals::$inst { @@ -1621,6 +1639,10 @@ foreach_peripheral!( static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new(); &WAKER } + + fn kernel_clk() -> Hertz { + kernel_clk!($inst) + } } impl Instance for peripherals::$inst {} From 73ef85b7650eea65c2f52e570f26062dd8ec38d0 Mon Sep 17 00:00:00 2001 From: chemicstry Date: Thu, 23 Feb 2023 18:00:55 +0200 Subject: [PATCH 91/95] stm32/sdmmc: Fix compile errors --- embassy-stm32/src/sdmmc/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 2d91286f..03d24dcb 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -1580,7 +1580,7 @@ cfg_if::cfg_if! { // F1 uses AHB1(HCLK), which is correct in PAC macro_rules! kernel_clk { ($inst:ident) => { - peripherals::$inst::frequency() + ::frequency() } } } else if #[cfg(any(stm32f2, stm32f4))] { @@ -1619,7 +1619,7 @@ cfg_if::cfg_if! { // Use default peripheral clock and hope it works macro_rules! kernel_clk { ($inst:ident) => { - peripherals::$inst::frequency() + ::frequency() } } } From 43a4409405e0df2f53452ca5d3a19a7d13e0f234 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 23 Feb 2023 19:25:22 +0100 Subject: [PATCH 92/95] embassy-time: Implement conversions to/from core::time::Duration for embassy-time::Duration --- embassy-time/src/duration.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs index 9d0bab2d..8366455b 100644 --- a/embassy-time/src/duration.rs +++ b/embassy-time/src/duration.rs @@ -192,3 +192,19 @@ impl<'a> fmt::Display for Duration { const fn div_ceil(num: u64, den: u64) -> u64 { (num + den - 1) / den } + +impl TryFrom for Duration { + type Error = >::Error; + + /// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64. + fn try_from(value: core::time::Duration) -> Result { + Ok(Self::from_micros(value.as_micros().try_into()?)) + } +} + +impl From for core::time::Duration { + /// Converts using [`Duration::as_micros`]. + fn from(value: Duration) -> Self { + core::time::Duration::from_micros(value.as_micros()) + } +} From 7be4337de96de9948632bdc2fc5067d0c4a76b33 Mon Sep 17 00:00:00 2001 From: Grant Miller Date: Fri, 24 Feb 2023 13:01:41 -0600 Subject: [PATCH 93/95] Add `#[must_use]` to all futures --- embassy-futures/src/yield_now.rs | 1 + embassy-nrf/src/gpiote.rs | 1 + embassy-rp/src/dma.rs | 1 + embassy-rp/src/gpio.rs | 1 + embassy-rp/src/pio.rs | 3 +++ embassy-stm32/src/dma/mod.rs | 1 + embassy-stm32/src/exti.rs | 1 + embassy-sync/src/channel.rs | 4 ++++ embassy-sync/src/pipe.rs | 2 ++ embassy-time/src/timer.rs | 1 + 10 files changed, 16 insertions(+) diff --git a/embassy-futures/src/yield_now.rs b/embassy-futures/src/yield_now.rs index 13b10377..bb3c67d1 100644 --- a/embassy-futures/src/yield_now.rs +++ b/embassy-futures/src/yield_now.rs @@ -24,6 +24,7 @@ pub fn yield_now() -> impl Future { YieldNowFuture { yielded: false } } +#[must_use = "futures do nothing unless you `.await` or poll them"] struct YieldNowFuture { yielded: bool, } diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index e1816eb9..66c682b4 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -315,6 +315,7 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { // ======================= +#[must_use = "futures do nothing unless you `.await` or poll them"] pub(crate) struct PortInputFuture<'a> { pin: PeripheralRef<'a, AnyPin>, } diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index fd281fd5..05adcecd 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -151,6 +151,7 @@ fn copy_inner<'a, C: Channel>( Transfer::new(ch) } +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a, C: Channel> { channel: PeripheralRef<'a, C>, } diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 76d4281f..fd3b0556 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -193,6 +193,7 @@ unsafe fn IO_IRQ_BANK0() { } } +#[must_use = "futures do nothing unless you `.await` or poll them"] struct InputFuture<'a, T: Pin> { pin: PeripheralRef<'a, T>, level: InterruptTrigger, diff --git a/embassy-rp/src/pio.rs b/embassy-rp/src/pio.rs index 2fb2783d..3c7abea2 100644 --- a/embassy-rp/src/pio.rs +++ b/embassy-rp/src/pio.rs @@ -120,6 +120,7 @@ unsafe fn PIO1_IRQ_0() { } /// Future that waits for TX-FIFO to become writable +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct FifoOutFuture<'a, PIO: PioInstance, SM: PioStateMachine + Unpin> { sm: &'a mut SM, pio: PhantomData, @@ -182,6 +183,7 @@ impl<'d, PIO: PioInstance, SM: PioStateMachine + Unpin> Drop for FifoOutFuture<' } /// Future that waits for RX-FIFO to become readable +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct FifoInFuture<'a, PIO: PioInstance, SM: PioStateMachine> { sm: &'a mut SM, pio: PhantomData, @@ -241,6 +243,7 @@ impl<'d, PIO: PioInstance, SM: PioStateMachine> Drop for FifoInFuture<'d, PIO, S } /// Future that waits for IRQ +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct IrqFuture { pio: PhantomData, irq_no: u8, diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index f5a82fb7..0030bd57 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -273,6 +273,7 @@ mod transfers { Transfer::new(channel) } + #[must_use = "futures do nothing unless you `.await` or poll them"] pub(crate) struct Transfer<'a, C: Channel> { channel: PeripheralRef<'a, C>, } diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index c9c3ef62..e1ce09a4 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -198,6 +198,7 @@ mod eha { } } +#[must_use = "futures do nothing unless you `.await` or poll them"] struct ExtiInputFuture<'a> { pin: u8, phantom: PhantomData<&'a mut AnyPin>, diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 76f42d0e..77352874 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -181,6 +181,7 @@ where } /// Future returned by [`Channel::recv`] and [`Receiver::recv`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct RecvFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -203,6 +204,7 @@ where } /// Future returned by [`DynamicReceiver::recv`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct DynamicRecvFuture<'ch, T> { channel: &'ch dyn DynamicChannel, } @@ -219,6 +221,7 @@ impl<'ch, T> Future for DynamicRecvFuture<'ch, T> { } /// Future returned by [`Channel::send`] and [`Sender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct SendFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -250,6 +253,7 @@ where impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {} /// Future returned by [`DynamicSender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct DynamicSendFuture<'ch, T> { channel: &'ch dyn DynamicChannel, message: Option, diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index 905686ac..1977005f 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -48,6 +48,7 @@ where } /// Future returned by [`Pipe::write`] and [`Writer::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct WriteFuture<'p, M, const N: usize> where M: RawMutex, @@ -110,6 +111,7 @@ where } /// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct ReadFuture<'p, M, const N: usize> where M: RawMutex, diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs index bd791b81..f74b5cb2 100644 --- a/embassy-time/src/timer.rs +++ b/embassy-time/src/timer.rs @@ -26,6 +26,7 @@ pub async fn with_timeout(timeout: Duration, fut: F) -> Result Date: Sat, 25 Feb 2023 20:58:28 +0100 Subject: [PATCH 94/95] embassy-net: DNS resolver detects when name is just an IP address and returns immediately --- embassy-net/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index bda5f9e1..4ec1b5a7 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -236,6 +236,22 @@ impl Stack { /// Make a query for a given name and return the corresponding IP addresses. #[cfg(feature = "dns")] pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result, dns::Error> { + // For A and AAAA queries we try detect whether `name` is just an IP address + match qtype { + dns::DnsQueryType::A => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv4) { + return Ok([ip].into_iter().collect()); + } + } + #[cfg(feature = "proto-ipv6")] + dns::DnsQueryType::Aaaa => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv6) { + return Ok([ip].into_iter().collect()); + } + } + _ => {} + } + let query = poll_fn(|cx| { self.with_mut(|s, i| { let socket = s.sockets.get_mut::(i.dns_socket); From bc71230cd07296468f2e03c00f9ceddbab67c9d9 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 26 Feb 2023 21:50:12 +0100 Subject: [PATCH 95/95] examples/std: fix net running out of sockets. --- examples/std/src/bin/net.rs | 2 +- examples/std/src/bin/net_dns.rs | 2 +- examples/std/src/bin/net_udp.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 451850d9..e018e18c 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -65,7 +65,7 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<3>::new()), seed)); // Launch network task spawner.spawn(net_task(stack)).unwrap(); diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs index e1cc45a3..d1e1f821 100644 --- a/examples/std/src/bin/net_dns.rs +++ b/examples/std/src/bin/net_dns.rs @@ -65,7 +65,7 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack: &Stack<_> = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + let stack: &Stack<_> = &*singleton!(Stack::new(device, config, singleton!(StackResources::<3>::new()), seed)); // Launch network task spawner.spawn(net_task(stack)).unwrap(); diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index f1923f18..328a0536 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs @@ -62,7 +62,7 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<3>::new()), seed)); // Launch network task spawner.spawn(net_task(stack)).unwrap();