diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index a341e10d..0c708464 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -1,10 +1,13 @@ //! CDC-ACM class implementation, aka Serial over USB. -use core::cell::Cell; +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; @@ -76,6 +79,9 @@ struct ControlShared { line_coding: CriticalSectionMutex>, dtr: AtomicBool, rts: AtomicBool, + + waker: RefCell, + changed: AtomicBool, } impl Default for ControlShared { @@ -89,10 +95,28 @@ impl Default for ControlShared { parity_type: ParityType::None, data_rate: 8_000, })), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), } } } +impl ControlShared { + async fn changed(&self) { + poll_fn(|cx| match self.changed.load(Ordering::Relaxed) { + true => { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } + false => { + self.waker.borrow_mut().register(cx.waker()); + Poll::Pending + } + }) + .await + } +} + impl<'a> Control<'a> { fn shared(&mut self) -> &'a ControlShared { self.shared @@ -105,6 +129,9 @@ impl<'d> Handler for Control<'d> { shared.line_coding.lock(|x| x.set(LineCoding::default())); shared.dtr.store(false, Ordering::Relaxed); shared.rts.store(false, Ordering::Relaxed); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); } fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { @@ -127,9 +154,13 @@ impl<'d> Handler for Control<'d> { parity_type: data[5].into(), data_bits: data[6], }; - self.shared().line_coding.lock(|x| x.set(coding)); + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(coding)); debug!("Set line coding to: {:?}", coding); + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + Some(OutResponse::Accepted) } REQ_SET_CONTROL_LINE_STATE => { @@ -141,6 +172,9 @@ impl<'d> Handler for Control<'d> { shared.rts.store(rts, Ordering::Relaxed); debug!("Set dtr {}, rts {}", dtr, rts); + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + Some(OutResponse::Accepted) } _ => Some(OutResponse::Rejected), @@ -292,6 +326,38 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { }, ) } + + /// Split the class into sender, receiver and control + /// + /// Allows concurrently sending and receiving packets whilst monitoring for + /// control changes (dtr, rts) + pub fn split_with_control(self) -> (Sender<'d, D>, Receiver<'d, D>, ControlChanged<'d>) { + ( + Sender { + write_ep: self.write_ep, + control: self.control, + }, + Receiver { + read_ep: self.read_ep, + control: self.control, + }, + ControlChanged { control: self.control }, + ) + } +} + +/// CDC ACM Control status change monitor +/// +/// You can obtain a `ControlChanged` with [`CdcAcmClass::split_with_control`] +pub struct ControlChanged<'d> { + control: &'d ControlShared, +} + +impl<'d> ControlChanged<'d> { + /// Return a future for when the control settings change + pub async fn control_changed(&self) { + self.control.changed().await + } } /// CDC ACM class packet sender.