From 4a8f117f2520df9d1919cbbac3d2840ea1539e04 Mon Sep 17 00:00:00 2001 From: huntc Date: Thu, 16 Jun 2022 16:08:58 +1000 Subject: [PATCH] Puts in the machinery to handle power detected/removed --- embassy-nrf/src/usb.rs | 70 ++++++++++++++++++++++-- embassy-stm32/src/usb/usb.rs | 87 +++++++++++++++++------------- embassy-usb/src/driver.rs | 6 +++ embassy-usb/src/lib.rs | 20 +++++-- embassy-usb/src/util.rs | 68 ----------------------- examples/nrf/src/bin/usb_serial.rs | 38 ++----------- 6 files changed, 144 insertions(+), 145 deletions(-) delete mode 100644 embassy-usb/src/util.rs diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 6c872581d..88af74fc0 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -2,7 +2,7 @@ use core::marker::PhantomData; use core::mem::MaybeUninit; -use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; +use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; use core::task::Poll; use cortex_m::peripheral::NVIC; @@ -25,6 +25,7 @@ static EP0_WAKER: AtomicWaker = NEW_AW; 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); +static POWER_AVAILABLE: AtomicBool = AtomicBool::new(false); pub struct Driver<'d, T: Instance> { phantom: PhantomData<&'d mut T>, @@ -39,6 +40,11 @@ impl<'d, T: Instance> Driver<'d, T> { irq.unpend(); irq.enable(); + // Initialize the bus so that it signals that power is available. + // Not required when using with_power_management as we then rely on the irq. + POWER_AVAILABLE.store(true, Ordering::Relaxed); + BUS_WAKER.wake(); + Self { phantom: PhantomData, alloc_in: Allocator::new(), @@ -46,6 +52,25 @@ impl<'d, T: Instance> Driver<'d, T> { } } + /// Establish a new device that then uses the POWER peripheral to + /// detect USB power detected/removed events are handled. + #[cfg(not(feature = "_nrf5340-app"))] + pub fn with_power_management( + _usb: impl Unborrow + 'd, + irq: impl Unborrow + 'd, + power_irq: impl Interrupt, + ) -> Self { + let regs = unsafe { &*pac::POWER::ptr() }; + + power_irq.set_handler(Self::on_power_interrupt); + power_irq.unpend(); + power_irq.enable(); + + regs.intenset.write(|w| w.usbdetected().set().usbremoved().set()); + + Self::new(_usb, irq) + } + fn on_interrupt(_: *mut ()) { let regs = T::regs(); @@ -76,7 +101,6 @@ impl<'d, T: Instance> Driver<'d, T> { // doesn't cause an infinite irq loop. if regs.events_usbevent.read().bits() != 0 { regs.events_usbevent.reset(); - //regs.intenclr.write(|w| w.usbevent().clear()); BUS_WAKER.wake(); } @@ -96,6 +120,31 @@ impl<'d, T: Instance> Driver<'d, T> { } } } + + #[cfg(not(feature = "_nrf5340-app"))] + fn on_power_interrupt(_: *mut ()) { + let regs = unsafe { &*pac::POWER::ptr() }; + + let mut power_changed = false; + let mut power_available = false; + + if regs.events_usbdetected.read().bits() != 0 { + regs.events_usbdetected.reset(); + power_changed = true; + power_available = true; + } + + if regs.events_usbremoved.read().bits() != 0 { + regs.events_usbremoved.reset(); + power_changed = true; + power_available = false; + } + + if power_changed { + POWER_AVAILABLE.store(power_available, Ordering::Relaxed); + BUS_WAKER.wake(); + } + } } impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { @@ -138,7 +187,10 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { ( - Bus { phantom: PhantomData }, + Bus { + phantom: PhantomData, + power_available: false, + }, ControlPipe { _phantom: PhantomData, max_packet_size: control_max_packet_size, @@ -149,6 +201,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { pub struct Bus<'d, T: Instance> { phantom: PhantomData<&'d mut T>, + power_available: bool, } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { @@ -246,6 +299,17 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { trace!("USB event: ready"); } + if POWER_AVAILABLE.load(Ordering::Relaxed) != self.power_available { + self.power_available = !self.power_available; + if self.power_available { + trace!("Power event: available"); + return Poll::Ready(Event::PowerDetected); + } else { + trace!("Power event: removed"); + return Poll::Ready(Event::PowerRemoved); + } + } + Poll::Pending }) } diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index cadbb423c..4633ff00c 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -161,6 +161,9 @@ impl<'d, T: Instance> Driver<'d, T> { dm.set_as_af(dm.af_num(), AFType::OutputPushPull); } + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + Self { phantom: PhantomData, alloc: [EndpointData { @@ -406,6 +409,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { Bus { phantom: PhantomData, ep_types, + inited: false, }, ControlPipe { _phantom: PhantomData, @@ -420,6 +424,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { pub struct Bus<'d, T: Instance> { phantom: PhantomData<&'d mut T>, ep_types: [EpType; EP_COUNT - 1], + inited: bool, } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { @@ -428,53 +433,59 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { poll_fn(move |cx| unsafe { BUS_WAKER.register(cx.waker()); - let regs = T::regs(); - let flags = IRQ_FLAGS.load(Ordering::Acquire); + if self.inited { + let regs = T::regs(); - if flags & IRQ_FLAG_RESUME != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel); - return Poll::Ready(Event::Resume); - } + let flags = IRQ_FLAGS.load(Ordering::Acquire); - if flags & IRQ_FLAG_RESET != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel); - - trace!("RESET REGS WRITINGINGING"); - regs.daddr().write(|w| { - w.set_ef(true); - w.set_add(0); - }); - - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat::NAK); - w.set_stat_tx(Stat::NAK); - }); - - for i in 1..EP_COUNT { - regs.epr(i).write(|w| { - w.set_ea(i as _); - w.set_ep_type(self.ep_types[i - 1]); - }) + if flags & IRQ_FLAG_RESUME != 0 { + IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel); + return Poll::Ready(Event::Resume); } - for w in &EP_IN_WAKERS { - w.wake() - } - for w in &EP_OUT_WAKERS { - w.wake() + if flags & IRQ_FLAG_RESET != 0 { + IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel); + + trace!("RESET REGS WRITINGINGING"); + regs.daddr().write(|w| { + w.set_ef(true); + w.set_add(0); + }); + + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::NAK); + w.set_stat_tx(Stat::NAK); + }); + + for i in 1..EP_COUNT { + regs.epr(i).write(|w| { + w.set_ea(i as _); + w.set_ep_type(self.ep_types[i - 1]); + }) + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + + return Poll::Ready(Event::Reset); } - return Poll::Ready(Event::Reset); - } + if flags & IRQ_FLAG_SUSPEND != 0 { + IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel); + return Poll::Ready(Event::Suspend); + } - if flags & IRQ_FLAG_SUSPEND != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel); - return Poll::Ready(Event::Suspend); + Poll::Pending + } else { + self.inited = true; + return Poll::Ready(Event::PowerDetected); } - - Poll::Pending }) } diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index bfe44d627..2a84ff9e7 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -202,6 +202,12 @@ pub enum Event { /// A USB resume request has been detected after being suspended or, in the case of self-powered /// devices, the device has been connected to the USB bus. Resume, + + /// The USB power has been detected. + PowerDetected, + + /// The USB power has been removed. Not supported by all devices. + PowerRemoved, } #[derive(Copy, Clone, Eq, PartialEq, Debug)] diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index eba46b892..af102e589 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -11,7 +11,6 @@ pub mod descriptor; mod descriptor_reader; pub mod driver; pub mod types; -pub mod util; use embassy::util::{select, Either}; use heapless::Vec; @@ -115,6 +114,7 @@ struct Inner<'d, D: Driver<'d>> { device_state: UsbDeviceState, suspended: bool, + power_available: bool, remote_wakeup_enabled: bool, self_powered: bool, @@ -157,6 +157,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { device_state: UsbDeviceState::Disabled, suspended: false, + power_available: false, remote_wakeup_enabled: false, self_powered: false, address: 0, @@ -186,6 +187,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { /// before calling any other `UsbDevice` methods to fully reset the /// peripheral. pub async fn run_until_suspend(&mut self) -> () { + while !self.inner.power_available { + let evt = self.inner.bus.poll().await; + self.inner.handle_bus_event(evt); + } + if self.inner.device_state == UsbDeviceState::Disabled { self.inner.bus.enable().await; self.inner.device_state = UsbDeviceState::Default; @@ -195,7 +201,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - while !self.inner.suspended { + while !self.inner.suspended && self.inner.power_available { let control_fut = self.control.setup(); let bus_fut = self.inner.bus.poll(); match select(bus_fut, control_fut).await { @@ -223,7 +229,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { /// /// This future is cancel-safe. pub async fn wait_resume(&mut self) { - while self.inner.suspended { + while self.inner.suspended || !self.inner.power_available { let evt = self.inner.bus.poll().await; self.inner.handle_bus_event(evt); } @@ -377,6 +383,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { h.suspended(true); } } + Event::PowerDetected => { + trace!("usb: power detected"); + self.power_available = true; + } + Event::PowerRemoved => { + trace!("usb: power removed"); + self.power_available = false; + } } } diff --git a/embassy-usb/src/util.rs b/embassy-usb/src/util.rs deleted file mode 100644 index ac56691b8..000000000 --- a/embassy-usb/src/util.rs +++ /dev/null @@ -1,68 +0,0 @@ -use embassy::channel::signal::Signal; -use embassy::util::{select, Either}; - -use crate::driver::Driver; -use crate::UsbDevice; - -/// Am enabled usb device is a device that further receives external notifications -/// regarding whether it is enabled or not. A common example of where this is -/// required is when receiving notifications from the POWER peripheral that -/// USB has been connected to or removed. The device here wraps an existing -/// USB device, keeping it publically available so that device-oriented operations -/// may still be performed. A signal is also provided that enables/disables the -/// USB device, taking care of suspension and resumption. In the case of the POWER -/// peripheral, this signal can be used from within a POWER_CLOCK interrupt -/// handler. Alternatively, for softdevice usage where the POWER peripheral is not -/// available, similar USB power events can be leveraged. -pub struct EnabledUsbDevice<'d, D: Driver<'d>> { - pub underlying: UsbDevice<'d, D>, - enable_usb_signal: &'d Signal, -} - -impl<'d, D: Driver<'d>> EnabledUsbDevice<'d, D> { - /// Wrap an existing UsbDevice and take a signal that will be used - /// to enable/disable it, perhaps from an external POWER_CLOCK - /// interrupt, or the equivalent when dealing with softdevices. - pub fn new(underlying: UsbDevice<'d, D>, enable_usb_signal: &'d Signal) -> Self { - Self { - underlying, - enable_usb_signal, - } - } - - /// Runs the underlying `UsbDevice` taking care of reacting to USB becoming - /// enabled/disabled. - /// - /// This future may leave the bus in an invalid state if it is dropped. - /// After dropping the future, [`UsbDevice::disable()`] should be called - /// before calling any other `UsbDevice` methods to fully reset the - /// peripheral. - pub async fn run(&mut self) -> ! { - while !self.enable_usb_signal.wait().await {} - loop { - match select( - self.underlying.run_until_suspend(), - self.enable_usb_signal.wait(), - ) - .await - { - Either::First(_) => {} - Either::Second(enable) => { - if !enable { - self.underlying.disable().await; - while !self.enable_usb_signal.wait().await {} - } - } - } - match select(self.underlying.wait_resume(), self.enable_usb_signal.wait()).await { - Either::First(_) => {} - Either::Second(enable) => { - if !enable { - self.underlying.disable().await; - while !self.enable_usb_signal.wait().await {} - } - } - } - } - } -} diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 377ae8c3a..7c1d8cbb8 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -6,47 +6,18 @@ use core::mem; use defmt::{info, panic}; -use embassy::channel::signal::Signal; use embassy::executor::Spawner; -use embassy::interrupt::InterruptExt; use embassy_nrf::usb::{Driver, Instance}; -use embassy_nrf::{interrupt, interrupt, pac, pac, Peripherals}; +use embassy_nrf::{interrupt, pac, Peripherals}; use embassy_usb::driver::EndpointError; -use embassy_usb::util::EnabledUsbDevice; use embassy_usb::{Builder, Config}; use embassy_usb_serial::{CdcAcmClass, State}; use futures::future::join; -use {defmt_rtt as _, panic_probe as _}; // global logger - -static ENABLE_USB: Signal = Signal::new(); - -fn on_power_interrupt(_: *mut ()) { - let regs = unsafe { &*pac::POWER::ptr() }; - - if regs.events_usbdetected.read().bits() != 0 { - regs.events_usbdetected.reset(); - info!("Vbus detected, enabling USB..."); - ENABLE_USB.signal(true); - } - - if regs.events_usbremoved.read().bits() != 0 { - regs.events_usbremoved.reset(); - info!("Vbus removed, disabling USB..."); - ENABLE_USB.signal(false); - } -} +use {defmt_rtt as _, panic_probe as _}; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { let clock: pac::CLOCK = unsafe { mem::transmute(()) }; - let power: pac::POWER = unsafe { mem::transmute(()) }; - - let power_irq = interrupt::take!(POWER_CLOCK); - power_irq.set_handler(on_power_interrupt); - power_irq.unpend(); - power_irq.enable(); - - power.intenset.write(|w| w.usbdetected().set().usbremoved().set()); info!("Enabling ext hfosc..."); clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); @@ -54,7 +25,8 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); - let driver = Driver::new(p.USBD, irq); + let power_irq = interrupt::take!(POWER_CLOCK); + let driver = Driver::with_power_management(p.USBD, irq, power_irq); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -94,7 +66,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut usb = EnabledUsbDevice::new(builder.build(), &ENABLE_USB); + let mut usb = builder.build(); // Run the USB device. let usb_fut = usb.run();