summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-nrf/src/usb.rs180
-rw-r--r--embassy-usb-serial/src/lib.rs5
-rw-r--r--embassy-usb/src/driver.rs12
-rw-r--r--embassy-usb/src/lib.rs7
-rw-r--r--examples/nrf/src/bin/usb_serial.rs46
5 files changed, 190 insertions, 60 deletions
diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs
index 124316a2..c16e1be0 100644
--- a/embassy-nrf/src/usb.rs
+++ b/embassy-nrf/src/usb.rs
@@ -17,6 +17,7 @@ use futures::future::poll_fn;
use futures::Future;
pub use embassy_usb;
+use pac::usbd::RegisterBlock;
use crate::interrupt::Interrupt;
use crate::pac;
@@ -92,11 +93,11 @@ impl<'d, T: Instance> Driver<'d, T> {
regs.epdatastatus.write(|w| unsafe { w.bits(r) });
READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel);
for i in 1..=7 {
- if r & (1 << i) != 0 {
- EP_IN_WAKERS[i - 1].wake();
+ if r & In::mask(i) != 0 {
+ In::waker(i).wake();
}
- if r & (1 << (i + 16)) != 0 {
- EP_OUT_WAKERS[i - 1].wake();
+ if r & Out::mask(i) != 0 {
+ Out::waker(i).wake();
}
}
}
@@ -272,32 +273,48 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
#[inline]
fn reset(&mut self) {
- let regs = T::regs();
+ self.set_configured(false);
+ }
- // TODO: Initialize ISO buffers
+ #[inline]
+ fn set_configured(&mut self, configured: bool) {
+ let regs = T::regs();
- // XXX this is not spec compliant; the endpoints should only be enabled after the device
- // has been put in the Configured state. However, usb-device provides no hook to do that
unsafe {
- regs.epinen.write(|w| w.bits(self.alloc_in.used.into()));
- regs.epouten.write(|w| w.bits(self.alloc_out.used.into()));
- }
+ if configured {
+ // TODO: Initialize ISO buffers
- for i in 1..8 {
- let out_enabled = self.alloc_out.used & (1 << i) != 0;
+ regs.epinen.write(|w| w.bits(self.alloc_in.used.into()));
+ regs.epouten.write(|w| w.bits(self.alloc_out.used.into()));
+
+ for i in 1..8 {
+ let out_enabled = self.alloc_out.used & (1 << i) != 0;
+
+ // when first enabled, bulk/interrupt OUT endpoints will *not* receive data (the
+ // peripheral will NAK all incoming packets) until we write a zero to the SIZE
+ // register (see figure 203 of the 52840 manual). To avoid that we write a 0 to the
+ // SIZE register
+ if out_enabled {
+ regs.size.epout[i].reset();
+ }
+ }
- // when first enabled, bulk/interrupt OUT endpoints will *not* receive data (the
- // peripheral will NAK all incoming packets) until we write a zero to the SIZE
- // register (see figure 203 of the 52840 manual). To avoid that we write a 0 to the
- // SIZE register
- if out_enabled {
- regs.size.epout[i].reset();
+ // IN endpoints (low bits) default to ready.
+ // OUT endpoints (high bits) default to NOT ready, they become ready when data comes in.
+ READY_ENDPOINTS.store(0x0000FFFF, Ordering::Release);
+ } else {
+ // Disable all endpoints except EP0
+ regs.epinen.write(|w| w.bits(0x01));
+ regs.epouten.write(|w| w.bits(0x01));
+
+ READY_ENDPOINTS.store(In::mask(0), Ordering::Release);
}
}
- // IN endpoints (low bits) default to ready.
- // OUT endpoints (high bits) default to NOT ready, they become ready when data comes in.
- READY_ENDPOINTS.store(0x0000FFFF, Ordering::Release);
+ for i in 1..=7 {
+ In::waker(i).wake();
+ Out::waker(i).wake();
+ }
}
#[inline]
@@ -332,6 +349,46 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
pub enum Out {}
pub enum In {}
+trait EndpointDir {
+ fn waker(i: usize) -> &'static AtomicWaker;
+ fn mask(i: usize) -> u32;
+ fn is_enabled(regs: &RegisterBlock, i: usize) -> bool;
+}
+
+impl EndpointDir for In {
+ #[inline]
+ fn waker(i: usize) -> &'static AtomicWaker {
+ &EP_IN_WAKERS[i - 1]
+ }
+
+ #[inline]
+ fn mask(i: usize) -> u32 {
+ 1 << i
+ }
+
+ #[inline]
+ fn is_enabled(regs: &RegisterBlock, i: usize) -> bool {
+ (regs.epinen.read().bits() & (1 << i)) != 0
+ }
+}
+
+impl EndpointDir for Out {
+ #[inline]
+ fn waker(i: usize) -> &'static AtomicWaker {
+ &EP_OUT_WAKERS[i - 1]
+ }
+
+ #[inline]
+ fn mask(i: usize) -> u32 {
+ 1 << (i + 16)
+ }
+
+ #[inline]
+ fn is_enabled(regs: &RegisterBlock, i: usize) -> bool {
+ (regs.epouten.read().bits() & (1 << i)) != 0
+ }
+}
+
pub struct Endpoint<'d, T: Instance, Dir> {
_phantom: PhantomData<(&'d mut T, Dir)>,
info: EndpointInfo,
@@ -346,7 +403,7 @@ impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> {
}
}
-impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> {
+impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir> {
fn info(&self) -> &EndpointInfo {
&self.info
}
@@ -358,6 +415,49 @@ impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> {
fn is_stalled(&self) -> bool {
Driver::<T>::is_stalled(self.info.addr)
}
+
+ type WaitEnabledFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+
+ fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> {
+ let i = self.info.addr.index();
+ assert!(i != 0);
+
+ poll_fn(move |cx| {
+ Dir::waker(i).register(cx.waker());
+ if Dir::is_enabled(T::regs(), i) {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ })
+ }
+}
+
+impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> {
+ async fn wait_data_ready(&mut self) -> Result<(), ()>
+ where
+ Dir: EndpointDir,
+ {
+ let i = self.info.addr.index();
+ assert!(i != 0);
+ poll_fn(|cx| {
+ Dir::waker(i).register(cx.waker());
+ let r = READY_ENDPOINTS.load(Ordering::Acquire);
+ if !Dir::is_enabled(T::regs(), i) {
+ Poll::Ready(Err(()))
+ } else if r & Dir::mask(i) != 0 {
+ Poll::Ready(Ok(()))
+ } else {
+ Poll::Pending
+ }
+ })
+ .await?;
+
+ // Mark as not ready
+ READY_ENDPOINTS.fetch_and(!Dir::mask(i), Ordering::AcqRel);
+
+ Ok(())
+ }
}
unsafe fn read_dma<T: Instance>(i: usize, buf: &mut [u8]) -> Result<usize, ReadError> {
@@ -449,20 +549,9 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
let i = self.info.addr.index();
assert!(i != 0);
- // Wait until ready
- poll_fn(|cx| {
- EP_OUT_WAKERS[i - 1].register(cx.waker());
- let r = READY_ENDPOINTS.load(Ordering::Acquire);
- if r & (1 << (i + 16)) != 0 {
- Poll::Ready(())
- } else {
- Poll::Pending
- }
- })
- .await;
-
- // Mark as not ready
- READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel);
+ self.wait_data_ready()
+ .await
+ .map_err(|_| ReadError::Disabled)?;
unsafe { read_dma::<T>(i, buf) }
}
@@ -477,20 +566,9 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
let i = self.info.addr.index();
assert!(i != 0);
- // Wait until ready.
- poll_fn(|cx| {
- EP_IN_WAKERS[i - 1].register(cx.waker());
- let r = READY_ENDPOINTS.load(Ordering::Acquire);
- if r & (1 << i) != 0 {
- Poll::Ready(())
- } else {
- Poll::Pending
- }
- })
- .await;
-
- // Mark as not ready
- READY_ENDPOINTS.fetch_and(!(1 << i), Ordering::AcqRel);
+ self.wait_data_ready()
+ .await
+ .map_err(|_| WriteError::Disabled)?;
unsafe { write_dma::<T>(i, buf) }
diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs
index 8418de0f..07352fac 100644
--- a/embassy-usb-serial/src/lib.rs
+++ b/embassy-usb-serial/src/lib.rs
@@ -273,6 +273,11 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, ReadError> {
self.read_ep.read(data).await
}
+
+ /// Waits for the USB host to enable this interface
+ pub async fn wait_connection(&mut self) {
+ self.read_ep.wait_enabled().await
+ }
}
/// Number of stop bits for LineCoding
diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs
index 82b59bd1..6eaa40b0 100644
--- a/embassy-usb/src/driver.rs
+++ b/embassy-usb/src/driver.rs
@@ -72,6 +72,9 @@ pub trait Bus {
/// Sets the device USB address to `addr`.
fn set_device_address(&mut self, addr: u8);
+ /// Sets the device configured state.
+ fn set_configured(&mut self, configured: 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.
fn set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool);
@@ -105,6 +108,10 @@ pub trait Bus {
}
pub trait Endpoint {
+ type WaitEnabledFuture<'a>: Future<Output = ()> + 'a
+ where
+ Self: 'a;
+
/// Get the endpoint address
fn info(&self) -> &EndpointInfo;
@@ -115,6 +122,9 @@ pub trait Endpoint {
/// Gets whether the STALL condition is set for an endpoint.
fn is_stalled(&self) -> bool;
+ /// Waits for the endpoint to be enabled.
+ fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_>;
+
// TODO enable/disable?
}
@@ -212,6 +222,7 @@ pub enum WriteError {
/// class shouldn't provide more data than the `max_packet_size` it specified when allocating
/// the endpoint.
BufferOverflow,
+ Disabled,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
@@ -223,4 +234,5 @@ pub enum ReadError {
/// should use a buffer that is large enough for the `max_packet_size` it specified when
/// allocating the endpoint.
BufferOverflow,
+ Disabled,
}
diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs
index d2d3e5e0..cf8d1253 100644
--- a/embassy-usb/src/lib.rs
+++ b/embassy-usb/src/lib.rs
@@ -162,18 +162,21 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
self.remote_wakeup_enabled = true;
self.control.accept(stage)
}
- (Request::SET_ADDRESS, 1..=127) => {
- self.pending_address = req.value as u8;
+ (Request::SET_ADDRESS, addr @ 1..=127) => {
+ self.pending_address = addr as u8;
+ self.bus.set_device_address(self.pending_address);
self.control.accept(stage)
}
(Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => {
self.device_state = UsbDeviceState::Configured;
+ self.bus.set_configured(true);
self.control.accept(stage)
}
(Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => match self.device_state {
UsbDeviceState::Default => self.control.accept(stage),
_ => {
self.device_state = UsbDeviceState::Addressed;
+ self.bus.set_configured(false);
self.control.accept(stage)
}
},
diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs
index cd681c5c..9437e835 100644
--- a/examples/nrf/src/bin/usb_serial.rs
+++ b/examples/nrf/src/bin/usb_serial.rs
@@ -4,12 +4,13 @@
#![feature(type_alias_impl_trait)]
use core::mem;
-use defmt::*;
+use defmt::{info, panic};
use embassy::executor::Spawner;
use embassy_nrf::interrupt;
use embassy_nrf::pac;
-use embassy_nrf::usb::Driver;
+use embassy_nrf::usb::{Driver, Instance};
use embassy_nrf::Peripherals;
+use embassy_usb::driver::{ReadError, WriteError};
use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_serial::{CdcAcmClass, State};
use futures::future::join;
@@ -66,12 +67,11 @@ async fn main(_spawner: Spawner, p: Peripherals) {
// Do stuff with the class!
let echo_fut = async {
- let mut buf = [0; 64];
loop {
- let n = class.read_packet(&mut buf).await.unwrap();
- let data = &buf[..n];
- info!("data: {:x}", data);
- class.write_packet(data).await.unwrap();
+ class.wait_connection().await;
+ info!("Connected");
+ let _ = echo(&mut class).await;
+ info!("Disconnected");
}
};
@@ -79,3 +79,35 @@ async fn main(_spawner: Spawner, p: Peripherals) {
// 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<ReadError> for Disconnected {
+ fn from(val: ReadError) -> Self {
+ match val {
+ ReadError::BufferOverflow => panic!("Buffer overflow"),
+ ReadError::Disabled => Disconnected {},
+ }
+ }
+}
+
+impl From<WriteError> for Disconnected {
+ fn from(val: WriteError) -> Self {
+ match val {
+ WriteError::BufferOverflow => panic!("Buffer overflow"),
+ WriteError::Disabled => Disconnected {},
+ }
+ }
+}
+
+async fn echo<'d, T: Instance + 'd>(
+ class: &mut CdcAcmClass<'d, Driver<'d, T>>,
+) -> Result<(), Disconnected> {
+ let mut buf = [0; 64];
+ loop {
+ let n = class.read_packet(&mut buf).await?;
+ let data = &buf[..n];
+ info!("data: {:x}", data);
+ class.write_packet(data).await?;
+ }
+}