657: Async usb stack r=Dirbaio a=Dirbaio

TODO

- [x] Make it work on nRF
- [x] Add a way for classes to handle their own EP0 control requests - thanks `@alexmoon!`
- [x] Handle CONTROL OUT requests with data.
- [ ] Impl AsyncRead/AsyncWrite for CDC ACM -- will do later, it's not trivial
- [x] Cleanup unwraps/asserts/panics
- [x] Cleanup logs (make everything trace/debug, not info)
- [ ] Port synopsys-usb-otg
- [ ] Port stm32-usbd
- [ ] Add more classes? HID, MSD?


Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Co-authored-by: alexmoon <alex.r.moon@gmail.com>
This commit is contained in:
bors[bot] 2022-04-07 23:03:39 +00:00 committed by GitHub
commit 37da84129d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 4855 additions and 191 deletions

View File

@ -37,7 +37,7 @@ jobs:
key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
- name: build
run: |
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.2.0/cargo-batch
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
chmod +x /usr/local/bin/cargo-batch
./ci.sh
rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
@ -59,7 +59,7 @@ jobs:
key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
- name: build
run: |
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.1.0/cargo-batch
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
chmod +x /usr/local/bin/cargo-batch
./ci_stable.sh
rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*

View File

@ -18,7 +18,7 @@ flavors = [
[features]
# Enable nightly-only features
nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async"]
nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-usb"]
# Reexport the PAC for the currently enabled chip at `embassy_nrf::pac`.
# This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version.
@ -64,6 +64,7 @@ _gpio-p1 = []
embassy = { version = "0.1.0", path = "../embassy" }
embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["nrf"]}
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
embassy-usb = {version = "0.1.0", path = "../embassy-usb", optional=true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.7", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy2", optional = true}
@ -80,8 +81,6 @@ rand_core = "0.6.3"
fixed = "1.10.0"
embedded-storage = "0.3.0"
cfg-if = "1.0.0"
nrf-usbd = {version = "0.1.1"}
usb-device = "0.2.8"
nrf52805-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }

View File

@ -125,6 +125,7 @@ embassy_hal_common::peripherals! {
TEMP,
}
#[cfg(feature = "nightly")]
impl_usb!(USBD, USBD, USBD);
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);

View File

@ -157,6 +157,7 @@ embassy_hal_common::peripherals! {
TEMP,
}
#[cfg(feature = "nightly")]
impl_usb!(USBD, USBD, USBD);
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);

View File

@ -160,6 +160,7 @@ embassy_hal_common::peripherals! {
TEMP,
}
#[cfg(feature = "nightly")]
impl_usb!(USBD, USBD, USBD);
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);

View File

@ -348,6 +348,7 @@ embassy_hal_common::peripherals! {
P1_15,
}
#[cfg(feature = "nightly")]
impl_usb!(USBD, USBD, USBD);
impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0);

View File

@ -91,6 +91,7 @@ pub mod uarte;
feature = "nrf52833",
feature = "nrf52840"
))]
#[cfg(feature = "nightly")]
pub mod usb;
#[cfg(not(feature = "_nrf5340"))]
pub mod wdt;

View File

@ -1,43 +1,810 @@
#![macro_use]
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
use core::task::Poll;
use cortex_m::peripheral::NVIC;
use embassy::interrupt::InterruptExt;
use embassy::util::Unborrow;
use embassy::waitqueue::AtomicWaker;
use embassy_hal_common::unborrow;
use embassy_usb::control::Request;
use embassy_usb::driver::{self, Event, ReadError, WriteError};
use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection};
use futures::future::poll_fn;
use futures::Future;
pub use embassy_usb;
use pac::usbd::RegisterBlock;
use crate::interrupt::Interrupt;
use crate::pac;
use crate::util::slice_in_ram;
use core::marker::PhantomData;
use embassy::util::Unborrow;
use nrf_usbd::{UsbPeripheral, Usbd};
use usb_device::bus::UsbBusAllocator;
const NEW_AW: AtomicWaker = AtomicWaker::new();
static BUS_WAKER: AtomicWaker = NEW_AW;
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);
pub use embassy_hal_common::usb::*;
pub struct UsbBus<'d, T: Instance> {
pub struct Driver<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
alloc_in: Allocator,
alloc_out: Allocator,
}
unsafe impl<'d, T: Instance> UsbPeripheral for UsbBus<'d, T> {
// todo how to use T::regs
const REGISTERS: *const () = pac::USBD::ptr() as *const ();
}
impl<'d, T: Instance> Driver<'d, T> {
pub fn new(
_usb: impl Unborrow<Target = T> + 'd,
irq: impl Unborrow<Target = T::Interrupt> + 'd,
) -> Self {
unborrow!(irq);
irq.set_handler(Self::on_interrupt);
irq.unpend();
irq.enable();
impl<'d, T: Instance> UsbBus<'d, T> {
pub fn new(_usb: impl Unborrow<Target = T> + 'd) -> UsbBusAllocator<Usbd<UsbBus<'d, T>>> {
let r = T::regs();
r.intenset.write(|w| {
w.sof().set_bit();
w.usbevent().set_bit();
w.ep0datadone().set_bit();
w.ep0setup().set_bit();
w.usbreset().set_bit()
});
Usbd::new(UsbBus {
Self {
phantom: PhantomData,
alloc_in: Allocator::new(),
alloc_out: Allocator::new(),
}
}
fn on_interrupt(_: *mut ()) {
let regs = T::regs();
if regs.events_usbreset.read().bits() != 0 {
regs.intenclr.write(|w| w.usbreset().clear());
BUS_WAKER.wake();
EP0_WAKER.wake();
}
if regs.events_ep0setup.read().bits() != 0 {
regs.intenclr.write(|w| w.ep0setup().clear());
EP0_WAKER.wake();
}
if regs.events_ep0datadone.read().bits() != 0 {
regs.intenclr.write(|w| w.ep0datadone().clear());
EP0_WAKER.wake();
}
// USBEVENT and EPDATA events are weird. They're the "aggregate"
// of individual bits in EVENTCAUSE and EPDATASTATUS. We handle them
// differently than events normally.
//
// They seem to be edge-triggered, not level-triggered: when an
// individual bit goes 0->1, the event fires *just once*.
// Therefore, it's fine to clear just the event, and let main thread
// check the individual bits in EVENTCAUSE and EPDATASTATUS. It
// 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();
}
if regs.events_epdata.read().bits() != 0 {
regs.events_epdata.reset();
let r = regs.epdatastatus.read().bits();
regs.epdatastatus.write(|w| unsafe { w.bits(r) });
READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel);
for i in 1..=7 {
if r & In::mask(i) != 0 {
In::waker(i).wake();
}
if r & Out::mask(i) != 0 {
Out::waker(i).wake();
}
}
}
}
fn set_stalled(ep_addr: EndpointAddress, stalled: bool) {
let regs = T::regs();
unsafe {
if ep_addr.index() == 0 {
regs.tasks_ep0stall
.write(|w| w.tasks_ep0stall().bit(stalled));
} else {
regs.epstall.write(|w| {
w.ep().bits(ep_addr.index() as u8 & 0b111);
w.io().bit(ep_addr.is_in());
w.stall().bit(stalled)
});
}
}
//if stalled {
// self.busy_in_endpoints &= !(1 << ep_addr.index());
//}
}
fn is_stalled(ep_addr: EndpointAddress) -> bool {
let regs = T::regs();
let i = ep_addr.index();
match ep_addr.direction() {
UsbDirection::Out => regs.halted.epout[i].read().getstatus().is_halted(),
UsbDirection::In => regs.halted.epin[i].read().getstatus().is_halted(),
}
}
}
impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
type EndpointOut = Endpoint<'d, T, Out>;
type EndpointIn = Endpoint<'d, T, In>;
type ControlPipe = ControlPipe<'d, T>;
type Bus = Bus<'d, T>;
type EnableFuture = impl Future<Output = Self::Bus> + 'd;
fn alloc_endpoint_in(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
let index = self
.alloc_in
.allocate(ep_addr, ep_type, max_packet_size, interval)?;
let ep_addr = EndpointAddress::from_parts(index, UsbDirection::In);
Ok(Endpoint::new(EndpointInfo {
addr: ep_addr,
ep_type,
max_packet_size,
interval,
}))
}
fn alloc_endpoint_out(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
let index = self
.alloc_out
.allocate(ep_addr, ep_type, max_packet_size, interval)?;
let ep_addr = EndpointAddress::from_parts(index, UsbDirection::Out);
Ok(Endpoint::new(EndpointInfo {
addr: ep_addr,
ep_type,
max_packet_size,
interval,
}))
}
fn alloc_control_pipe(
&mut self,
max_packet_size: u16,
) -> Result<Self::ControlPipe, driver::EndpointAllocError> {
self.alloc_endpoint_out(Some(0x00.into()), EndpointType::Control, max_packet_size, 0)?;
self.alloc_endpoint_in(Some(0x80.into()), EndpointType::Control, max_packet_size, 0)?;
Ok(ControlPipe {
_phantom: PhantomData,
max_packet_size,
})
}
fn enable(self) -> Self::EnableFuture {
async move {
let regs = T::regs();
errata::pre_enable();
regs.enable.write(|w| w.enable().enabled());
// Wait until the peripheral is ready.
regs.intenset.write(|w| w.usbevent().set_bit());
poll_fn(|cx| {
BUS_WAKER.register(cx.waker());
if regs.eventcause.read().ready().is_ready() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear.
errata::post_enable();
unsafe { NVIC::unmask(pac::Interrupt::USBD) };
regs.intenset.write(|w| {
w.usbreset().set_bit();
w.usbevent().set_bit();
w.epdata().set_bit();
w
});
// Enable the USB pullup, allowing enumeration.
regs.usbpullup.write(|w| w.connect().enabled());
trace!("enabled");
Bus {
phantom: PhantomData,
alloc_in: self.alloc_in,
alloc_out: self.alloc_out,
}
}
}
}
pub struct Bus<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
alloc_in: Allocator,
alloc_out: Allocator,
}
impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
type PollFuture<'a> = impl Future<Output = Event> + 'a where Self: 'a;
fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> {
poll_fn(|cx| {
BUS_WAKER.register(cx.waker());
let regs = T::regs();
if regs.events_usbreset.read().bits() != 0 {
regs.events_usbreset.reset();
regs.intenset.write(|w| w.usbreset().set());
return Poll::Ready(Event::Reset);
}
let r = regs.eventcause.read();
if r.isooutcrc().bit() {
regs.eventcause.write(|w| w.isooutcrc().set_bit());
trace!("USB event: isooutcrc");
}
if r.usbwuallowed().bit() {
regs.eventcause.write(|w| w.usbwuallowed().set_bit());
trace!("USB event: usbwuallowed");
}
if r.suspend().bit() {
regs.eventcause.write(|w| w.suspend().set_bit());
trace!("USB event: suspend");
}
if r.resume().bit() {
regs.eventcause.write(|w| w.resume().set_bit());
trace!("USB event: resume");
}
if r.ready().bit() {
regs.eventcause.write(|w| w.ready().set_bit());
trace!("USB event: ready");
}
Poll::Pending
})
}
#[inline]
fn reset(&mut self) {
self.set_configured(false);
}
#[inline]
fn set_configured(&mut self, configured: bool) {
let regs = T::regs();
unsafe {
if configured {
// TODO: Initialize ISO buffers
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();
}
}
// 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);
}
}
for i in 1..=7 {
In::waker(i).wake();
Out::waker(i).wake();
}
}
#[inline]
fn set_device_address(&mut self, _addr: u8) {
// Nothing to do, the peripheral handles this.
}
fn set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
Driver::<T>::set_stalled(ep_addr, stalled)
}
fn is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
Driver::<T>::is_stalled(ep_addr)
}
#[inline]
fn suspend(&mut self) {
let regs = T::regs();
regs.lowpower.write(|w| w.lowpower().low_power());
}
#[inline]
fn resume(&mut self) {
let regs = T::regs();
errata::pre_wakeup();
regs.lowpower.write(|w| w.lowpower().force_normal());
}
}
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,
}
impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> {
fn new(info: EndpointInfo) -> Self {
Self {
info,
_phantom: PhantomData,
}
}
}
impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir> {
fn info(&self) -> &EndpointInfo {
&self.info
}
fn set_stalled(&self, stalled: bool) {
Driver::<T>::set_stalled(self.info.addr, stalled)
}
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
}
})
}
}
unsafe impl embassy_hal_common::usb::USBInterrupt for crate::interrupt::USBD {}
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> {
let regs = T::regs();
// Check that the packet fits into the buffer
let size = regs.size.epout[i].read().bits() as usize;
if size > buf.len() {
return Err(ReadError::BufferOverflow);
}
if i == 0 {
regs.events_ep0datadone.reset();
}
let epout = [
&regs.epout0,
&regs.epout1,
&regs.epout2,
&regs.epout3,
&regs.epout4,
&regs.epout5,
&regs.epout6,
&regs.epout7,
];
epout[i].ptr.write(|w| w.bits(buf.as_ptr() as u32));
// MAXCNT must match SIZE
epout[i].maxcnt.write(|w| w.bits(size as u32));
dma_start();
regs.events_endepout[i].reset();
regs.tasks_startepout[i].write(|w| w.tasks_startepout().set_bit());
while regs.events_endepout[i]
.read()
.events_endepout()
.bit_is_clear()
{}
regs.events_endepout[i].reset();
dma_end();
regs.size.epout[i].reset();
Ok(size)
}
unsafe fn write_dma<T: Instance>(i: usize, buf: &[u8]) {
let regs = T::regs();
assert!(buf.len() <= 64);
let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit();
let ptr = if !slice_in_ram(buf) {
// EasyDMA can't read FLASH, so we copy through RAM
let ptr = ram_buf.as_mut_ptr() as *mut u8;
core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len());
ptr
} else {
buf.as_ptr()
};
let epin = [
&regs.epin0,
&regs.epin1,
&regs.epin2,
&regs.epin3,
&regs.epin4,
&regs.epin5,
&regs.epin6,
&regs.epin7,
];
// Set the buffer length so the right number of bytes are transmitted.
// Safety: `buf.len()` has been checked to be <= the max buffer length.
epin[i].ptr.write(|w| w.bits(ptr as u32));
epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8));
regs.events_endepin[i].reset();
dma_start();
regs.tasks_startepin[i].write(|w| w.bits(1));
while regs.events_endepin[i].read().bits() == 0 {}
dma_end();
}
impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
type ReadFuture<'a> = impl Future<Output = Result<usize, ReadError>> + 'a where Self: 'a;
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let i = self.info.addr.index();
assert!(i != 0);
self.wait_data_ready()
.await
.map_err(|_| ReadError::Disabled)?;
unsafe { read_dma::<T>(i, buf) }
}
}
}
impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
type WriteFuture<'a> = impl Future<Output = Result<(), WriteError>> + 'a where Self: 'a;
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
async move {
let i = self.info.addr.index();
assert!(i != 0);
self.wait_data_ready()
.await
.map_err(|_| WriteError::Disabled)?;
unsafe { write_dma::<T>(i, buf) }
Ok(())
}
}
}
pub struct ControlPipe<'d, T: Instance> {
_phantom: PhantomData<&'d mut T>,
max_packet_size: u16,
}
impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
type SetupFuture<'a> = impl Future<Output = Request> + 'a where Self: 'a;
type DataOutFuture<'a> = impl Future<Output = Result<usize, ReadError>> + 'a where Self: 'a;
type DataInFuture<'a> = impl Future<Output = Result<(), WriteError>> + 'a where Self: 'a;
fn max_packet_size(&self) -> usize {
usize::from(self.max_packet_size)
}
fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> {
async move {
let regs = T::regs();
// Wait for SETUP packet
regs.intenset.write(|w| {
w.ep0setup().set();
w.ep0datadone().set()
});
poll_fn(|cx| {
EP0_WAKER.register(cx.waker());
let regs = T::regs();
if regs.events_ep0setup.read().bits() != 0 {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
// Reset shorts
regs.shorts
.modify(|_, w| w.ep0datadone_ep0status().clear_bit());
regs.events_ep0setup.reset();
let mut buf = [0; 8];
buf[0] = regs.bmrequesttype.read().bits() as u8;
buf[1] = regs.brequest.read().brequest().bits();
buf[2] = regs.wvaluel.read().wvaluel().bits();
buf[3] = regs.wvalueh.read().wvalueh().bits();
buf[4] = regs.windexl.read().windexl().bits();
buf[5] = regs.windexh.read().windexh().bits();
buf[6] = regs.wlengthl.read().wlengthl().bits();
buf[7] = regs.wlengthh.read().wlengthh().bits();
let req = Request::parse(&buf);
if req.direction == UsbDirection::Out {
regs.tasks_ep0rcvout
.write(|w| w.tasks_ep0rcvout().set_bit());
}
req
}
}
fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a> {
async move {
let regs = T::regs();
// Wait until ready
regs.intenset.write(|w| {
w.usbreset().set();
w.ep0setup().set();
w.ep0datadone().set()
});
poll_fn(|cx| {
EP0_WAKER.register(cx.waker());
let regs = T::regs();
if regs.events_ep0datadone.read().bits() != 0 {
Poll::Ready(Ok(()))
} else if regs.events_usbreset.read().bits() != 0 {
trace!("aborted control data_out: usb reset");
Poll::Ready(Err(ReadError::Disabled))
} else if regs.events_ep0setup.read().bits() != 0 {
trace!("aborted control data_out: received another SETUP");
Poll::Ready(Err(ReadError::Disabled))
} else {
Poll::Pending
}
})
.await?;
unsafe { read_dma::<T>(0, buf) }
}
}
fn data_in<'a>(&'a mut self, buf: &'a [u8], last_packet: bool) -> Self::DataInFuture<'a> {
async move {
let regs = T::regs();
regs.events_ep0datadone.reset();
unsafe {
write_dma::<T>(0, buf);
}
regs.shorts
.modify(|_, w| w.ep0datadone_ep0status().bit(last_packet));
regs.intenset.write(|w| {
w.usbreset().set();
w.ep0setup().set();
w.ep0datadone().set()
});
poll_fn(|cx| {
cx.waker().wake_by_ref();
EP0_WAKER.register(cx.waker());
let regs = T::regs();
if regs.events_ep0datadone.read().bits() != 0 {
Poll::Ready(Ok(()))
} else if regs.events_usbreset.read().bits() != 0 {
trace!("aborted control data_in: usb reset");
Poll::Ready(Err(WriteError::Disabled))
} else if regs.events_ep0setup.read().bits() != 0 {
trace!("aborted control data_in: received another SETUP");
Poll::Ready(Err(WriteError::Disabled))
} else {
Poll::Pending
}
})
.await
}
}
fn accept(&mut self) {
let regs = T::regs();
regs.tasks_ep0status
.write(|w| w.tasks_ep0status().bit(true));
}
fn reject(&mut self) {
let regs = T::regs();
regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true));
}
}
fn dma_start() {
compiler_fence(Ordering::Release);
}
fn dma_end() {
compiler_fence(Ordering::Acquire);
}
struct Allocator {
used: u16,
// Buffers can be up to 64 Bytes since this is a Full-Speed implementation.
lens: [u8; 9],
}
impl Allocator {
fn new() -> Self {
Self {
used: 0,
lens: [0; 9],
}
}
fn allocate(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
_interval: u8,
) -> Result<usize, driver::EndpointAllocError> {
// Endpoint addresses are fixed in hardware:
// - 0x80 / 0x00 - Control EP0
// - 0x81 / 0x01 - Bulk/Interrupt EP1
// - 0x82 / 0x02 - Bulk/Interrupt EP2
// - 0x83 / 0x03 - Bulk/Interrupt EP3
// - 0x84 / 0x04 - Bulk/Interrupt EP4
// - 0x85 / 0x05 - Bulk/Interrupt EP5
// - 0x86 / 0x06 - Bulk/Interrupt EP6
// - 0x87 / 0x07 - Bulk/Interrupt EP7
// - 0x88 / 0x08 - Isochronous
// Endpoint directions are allocated individually.
let alloc_index = if let Some(ep_addr) = ep_addr {
match (ep_addr.index(), ep_type) {
(0, EndpointType::Control) => {}
(8, EndpointType::Isochronous) => {}
(n, EndpointType::Bulk) | (n, EndpointType::Interrupt) if n >= 1 && n <= 7 => {}
_ => return Err(driver::EndpointAllocError),
}
ep_addr.index()
} else {
match ep_type {
EndpointType::Isochronous => 8,
EndpointType::Control => 0,
EndpointType::Interrupt | EndpointType::Bulk => {
// Find rightmost zero bit in 1..=7
let ones = (self.used >> 1).trailing_ones() as usize;
if ones >= 7 {
return Err(driver::EndpointAllocError);
}
ones + 1
}
}
};
if self.used & (1 << alloc_index) != 0 {
return Err(driver::EndpointAllocError);
}
self.used |= 1 << alloc_index;
self.lens[alloc_index] = max_packet_size as u8;
Ok(alloc_index)
}
}
pub(crate) mod sealed {
use super::*;
@ -63,3 +830,64 @@ macro_rules! impl_usb {
}
};
}
mod errata {
/// Writes `val` to `addr`. Used to apply Errata workarounds.
unsafe fn poke(addr: u32, val: u32) {
(addr as *mut u32).write_volatile(val);
}
/// Reads 32 bits from `addr`.
unsafe fn peek(addr: u32) -> u32 {
(addr as *mut u32).read_volatile()
}
pub fn pre_enable() {
// Works around Erratum 187 on chip revisions 1 and 2.
unsafe {
poke(0x4006EC00, 0x00009375);
poke(0x4006ED14, 0x00000003);
poke(0x4006EC00, 0x00009375);
}
pre_wakeup();
}
pub fn post_enable() {
post_wakeup();
// Works around Erratum 187 on chip revisions 1 and 2.
unsafe {
poke(0x4006EC00, 0x00009375);
poke(0x4006ED14, 0x00000000);
poke(0x4006EC00, 0x00009375);
}
}
pub fn pre_wakeup() {
// Works around Erratum 171 on chip revisions 1 and 2.
unsafe {
if peek(0x4006EC00) == 0x00000000 {
poke(0x4006EC00, 0x00009375);
}
poke(0x4006EC14, 0x000000C0);
poke(0x4006EC00, 0x00009375);
}
}
pub fn post_wakeup() {
// Works around Erratum 171 on chip revisions 1 and 2.
unsafe {
if peek(0x4006EC00) == 0x00000000 {
poke(0x4006EC00, 0x00009375);
}
poke(0x4006EC14, 0x00000000);
poke(0x4006EC00, 0x00009375);
}
}
}

View File

@ -0,0 +1,18 @@
[package]
name = "embassy-usb-hid"
version = "0.1.0"
edition = "2021"
[features]
default = ["usbd-hid"]
usbd-hid = ["dep:usbd-hid", "ssmarshal"]
[dependencies]
embassy = { version = "0.1.0", path = "../embassy" }
embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
usbd-hid = { version = "0.5.2", optional = true }
ssmarshal = { version = "1.0", default-features = false, optional = true }
futures-util = { version = "0.3.21", default-features = false }

225
embassy-usb-hid/src/fmt.rs Normal file
View File

@ -0,0 +1,225 @@
#![macro_use]
#![allow(unused_macros)]
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}

522
embassy-usb-hid/src/lib.rs Normal file
View File

@ -0,0 +1,522 @@
#![no_std]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
//! Implements HID functionality for a usb-device device.
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
use core::mem::MaybeUninit;
use core::ops::Range;
use core::sync::atomic::{AtomicUsize, Ordering};
use embassy::time::Duration;
use embassy_usb::driver::EndpointOut;
use embassy_usb::{
control::{ControlHandler, InResponse, OutResponse, Request, RequestType},
driver::{Driver, Endpoint, EndpointIn, WriteError},
UsbDeviceBuilder,
};
#[cfg(feature = "usbd-hid")]
use ssmarshal::serialize;
#[cfg(feature = "usbd-hid")]
use usbd_hid::descriptor::AsInputReport;
const USB_CLASS_HID: u8 = 0x03;
const USB_SUBCLASS_NONE: u8 = 0x00;
const USB_PROTOCOL_NONE: u8 = 0x00;
// HID
const HID_DESC_DESCTYPE_HID: u8 = 0x21;
const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22;
const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01];
const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00;
const HID_REQ_SET_IDLE: u8 = 0x0a;
const HID_REQ_GET_IDLE: u8 = 0x02;
const HID_REQ_GET_REPORT: u8 = 0x01;
const HID_REQ_SET_REPORT: u8 = 0x09;
const HID_REQ_GET_PROTOCOL: u8 = 0x03;
const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ReportId {
In(u8),
Out(u8),
Feature(u8),
}
impl ReportId {
fn try_from(value: u16) -> Result<Self, ()> {
match value >> 8 {
1 => Ok(ReportId::In(value as u8)),
2 => Ok(ReportId::Out(value as u8)),
3 => Ok(ReportId::Feature(value as u8)),
_ => Err(()),
}
}
}
pub struct State<'a, const IN_N: usize, const OUT_N: usize> {
control: MaybeUninit<Control<'a>>,
out_report_offset: AtomicUsize,
}
impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> {
pub fn new() -> Self {
State {
control: MaybeUninit::uninit(),
out_report_offset: AtomicUsize::new(0),
}
}
}
pub struct HidClass<'d, D: Driver<'d>, T, const IN_N: usize> {
input: ReportWriter<'d, D, IN_N>,
output: T,
}
impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> {
/// Creates a new HidClass.
///
/// poll_ms configures how frequently the host should poll for reading/writing
/// HID reports. A lower value means better throughput & latency, at the expense
/// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
/// high performance uses, and a value of 255 is good for best-effort usecases.
///
/// This allocates an IN endpoint only.
pub fn new<const OUT_N: usize>(
builder: &mut UsbDeviceBuilder<'d, D>,
state: &'d mut State<'d, IN_N, OUT_N>,
report_descriptor: &'static [u8],
request_handler: Option<&'d dyn RequestHandler>,
poll_ms: u8,
max_packet_size: u16,
) -> Self {
let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms);
let control = state.control.write(Control::new(
report_descriptor,
request_handler,
&state.out_report_offset,
));
control.build(builder, None, &ep_in);
Self {
input: ReportWriter { ep_in },
output: (),
}
}
}
impl<'d, D: Driver<'d>, T, const IN_N: usize> HidClass<'d, D, T, IN_N> {
/// Gets the [`ReportWriter`] for input reports.
///
/// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out)
/// this writer will be useless as no endpoint is availabe to send reports.
pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> {
&mut self.input
}
}
impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize>
HidClass<'d, D, ReportReader<'d, D, OUT_N>, IN_N>
{
/// Creates a new HidClass.
///
/// poll_ms configures how frequently the host should poll for reading/writing
/// HID reports. A lower value means better throughput & latency, at the expense
/// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
/// high performance uses, and a value of 255 is good for best-effort usecases.
///
/// This allocates two endpoints (IN and OUT).
pub fn with_output_ep(
builder: &mut UsbDeviceBuilder<'d, D>,
state: &'d mut State<'d, IN_N, OUT_N>,
report_descriptor: &'static [u8],
request_handler: Option<&'d dyn RequestHandler>,
poll_ms: u8,
max_packet_size: u16,
) -> Self {
let ep_out = builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms);
let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms);
let control = state.control.write(Control::new(
report_descriptor,
request_handler,
&state.out_report_offset,
));
control.build(builder, Some(&ep_out), &ep_in);
Self {
input: ReportWriter { ep_in },
output: ReportReader {
ep_out,
offset: &state.out_report_offset,
},
}
}
/// Gets the [`ReportReader`] for output reports.
pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> {
&mut self.output
}
/// Splits this `HidClass` into seperate readers/writers for input and output reports.
pub fn split(self) -> (ReportWriter<'d, D, IN_N>, ReportReader<'d, D, OUT_N>) {
(self.input, self.output)
}
}
pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> {
ep_in: D::EndpointIn,
}
pub struct ReportReader<'d, D: Driver<'d>, const N: usize> {
ep_out: D::EndpointOut,
offset: &'d AtomicUsize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ReadError {
BufferOverflow,
Disabled,
Sync(Range<usize>),
}
impl From<embassy_usb::driver::ReadError> for ReadError {
fn from(val: embassy_usb::driver::ReadError) -> Self {
use embassy_usb::driver::ReadError::*;
match val {
BufferOverflow => ReadError::BufferOverflow,
Disabled => ReadError::Disabled,
}
}
}
impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> {
/// Waits for the interrupt in endpoint to be enabled.
pub async fn ready(&mut self) -> () {
self.ep_in.wait_enabled().await
}
/// Tries to write an input report by serializing the given report structure.
///
/// Panics if no endpoint is available.
#[cfg(feature = "usbd-hid")]
pub async fn serialize<IR: AsInputReport>(&mut self, r: &IR) -> Result<(), WriteError> {
let mut buf: [u8; N] = [0; N];
let size = match serialize(&mut buf, r) {
Ok(size) => size,
Err(_) => return Err(WriteError::BufferOverflow),
};
self.write(&buf[0..size]).await
}
/// Writes `report` to its interrupt endpoint.
///
/// Panics if no endpoint is available.
pub async fn write(&mut self, report: &[u8]) -> Result<(), WriteError> {
assert!(report.len() <= N);
let max_packet_size = usize::from(self.ep_in.info().max_packet_size);
let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0);
for chunk in report.chunks(max_packet_size) {
self.ep_in.write(chunk).await?;
}
if zlp_needed {
self.ep_in.write(&[]).await?;
}
Ok(())
}
}
impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> {
/// Waits for the interrupt out endpoint to be enabled.
pub async fn ready(&mut self) -> () {
self.ep_out.wait_enabled().await
}
/// Starts a task to deliver output reports from the Interrupt Out pipe to
/// `handler`.
///
/// Terminates when the interface becomes disabled.
///
/// If `use_report_ids` is true, the first byte of the report will be used as
/// the `ReportId` value. Otherwise the `ReportId` value will be 0.
pub async fn run<T: RequestHandler>(mut self, use_report_ids: bool, handler: &T) -> ! {
let offset = self.offset.load(Ordering::Acquire);
assert!(offset == 0);
let mut buf = [0; N];
loop {
match self.read(&mut buf).await {
Ok(len) => {
let id = if use_report_ids { buf[0] } else { 0 };
handler.set_report(ReportId::Out(id), &buf[..len]); }
Err(ReadError::BufferOverflow) => warn!("Host sent output report larger than the configured maximum output report length ({})", N),
Err(ReadError::Disabled) => self.ep_out.wait_enabled().await,
Err(ReadError::Sync(_)) => unreachable!(),
}
}
}
/// Reads an output report from the Interrupt Out pipe.
///
/// **Note:** Any reports sent from the host over the control pipe will be
/// passed to [`RequestHandler::set_report()`] for handling. The application
/// is responsible for ensuring output reports from both pipes are handled
/// correctly.
///
/// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output
/// reports may be split across multiple packets) and this method's future
/// is dropped after some packets have been read, the next call to `read()`
/// will return a [`ReadError::SyncError()`]. The range in the sync error
/// indicates the portion `buf` that was filled by the current call to
/// `read()`. If the dropped future used the same `buf`, then `buf` will
/// contain the full report.
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
assert!(N != 0);
assert!(buf.len() >= N);
// Read packets from the endpoint
let max_packet_size = usize::from(self.ep_out.info().max_packet_size);
let starting_offset = self.offset.load(Ordering::Acquire);
let mut total = starting_offset;
loop {
for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) {
match self.ep_out.read(chunk).await {
Ok(size) => {
total += size;
if size < max_packet_size || total == N {
self.offset.store(0, Ordering::Release);
break;
} else {
self.offset.store(total, Ordering::Release);
}
}
Err(err) => {
self.offset.store(0, Ordering::Release);
return Err(err.into());
}
}
}
// Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0.
if total > 0 {
break;
}
}
if starting_offset > 0 {
Err(ReadError::Sync(starting_offset..total))
} else {
Ok(total)
}
}
}
pub trait RequestHandler {
/// Reads the value of report `id` into `buf` returning the size.
///
/// Returns `None` if `id` is invalid or no data is available.
fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> {
let _ = (id, buf);
None
}
/// Sets the value of report `id` to `data`.
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
let _ = (id, data);
OutResponse::Rejected
}
/// Get the idle rate for `id`.
///
/// If `id` is `None`, get the idle rate for all reports. Returning `None`
/// will reject the control request. Any duration at or above 1.024 seconds
/// or below 4ms will be returned as an indefinite idle rate.
fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
let _ = id;
None
}
/// Set the idle rate for `id` to `dur`.
///
/// If `id` is `None`, set the idle rate of all input reports to `dur`. If
/// an indefinite duration is requested, `dur` will be set to `Duration::MAX`.
fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
let _ = (id, dur);
}
}
struct Control<'d> {
report_descriptor: &'static [u8],
request_handler: Option<&'d dyn RequestHandler>,
out_report_offset: &'d AtomicUsize,
hid_descriptor: [u8; 9],
}
impl<'a> Control<'a> {
fn new(
report_descriptor: &'static [u8],
request_handler: Option<&'a dyn RequestHandler>,
out_report_offset: &'a AtomicUsize,
) -> Self {
Control {
report_descriptor,
request_handler,
out_report_offset,
hid_descriptor: [
// Length of buf inclusive of size prefix
9,
// Descriptor type
HID_DESC_DESCTYPE_HID,
// HID Class spec version
HID_DESC_SPEC_1_10[0],
HID_DESC_SPEC_1_10[1],
// Country code not supported
HID_DESC_COUNTRY_UNSPEC,
// Number of following descriptors
1,
// We have a HID report descriptor the host should read
HID_DESC_DESCTYPE_HID_REPORT,
// HID report descriptor size,
(report_descriptor.len() & 0xFF) as u8,
(report_descriptor.len() >> 8 & 0xFF) as u8,
],
}
}
fn build<'d, D: Driver<'d>>(
&'d mut self,
builder: &mut UsbDeviceBuilder<'d, D>,
ep_out: Option<&D::EndpointOut>,
ep_in: &D::EndpointIn,
) {
let len = self.report_descriptor.len();
let if_num = builder.alloc_interface_with_handler(self);
builder.config_descriptor.interface(
if_num,
USB_CLASS_HID,
USB_SUBCLASS_NONE,
USB_PROTOCOL_NONE,
);
// HID descriptor
builder.config_descriptor.write(
HID_DESC_DESCTYPE_HID,
&[
// HID Class spec version
HID_DESC_SPEC_1_10[0],
HID_DESC_SPEC_1_10[1],
// Country code not supported
HID_DESC_COUNTRY_UNSPEC,
// Number of following descriptors
1,
// We have a HID report descriptor the host should read
HID_DESC_DESCTYPE_HID_REPORT,
// HID report descriptor size,
(len & 0xFF) as u8,
(len >> 8 & 0xFF) as u8,
],
);
builder.config_descriptor.endpoint(ep_in.info());
if let Some(ep) = ep_out {
builder.config_descriptor.endpoint(ep.info());
}
}
}
impl<'d> ControlHandler for Control<'d> {
fn reset(&mut self) {
self.out_report_offset.store(0, Ordering::Release);
}
fn control_out(&mut self, req: embassy_usb::control::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 = u64::from(req.value >> 8);
let dur = if dur == 0 {
Duration::MAX
} else {
Duration::from_millis(4 * dur)
};
handler.set_idle(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_type, req.request) {
(RequestType::Standard, Request::GET_DESCRIPTOR) => 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,
},
(RequestType::Class, 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
}
}
(RequestType::Class, 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(id) {
let dur = u8::try_from(dur.as_millis() / 4).unwrap_or(0);
buf[0] = dur;
InResponse::Accepted(&buf[0..1])
} else {
InResponse::Rejected
}
} else {
InResponse::Rejected
}
}
(RequestType::Class, HID_REQ_GET_PROTOCOL) => {
// UNSUPPORTED: Boot Protocol
buf[0] = 1;
InResponse::Accepted(&buf[0..1])
}
_ => InResponse::Rejected,
}
}
}

View File

@ -0,0 +1,19 @@
[package]
name = "embassy-usb-serial"
version = "0.1.0"
edition = "2021"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-serial-v$VERSION/embassy-usb-serial/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/master/embassy-usb-serial/src/"
features = ["defmt"]
flavors = [
{ name = "default", target = "thumbv7em-none-eabihf" },
]
[dependencies]
embassy = { version = "0.1.0", path = "../embassy" }
embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }

View File

@ -0,0 +1,225 @@
#![macro_use]
#![allow(unused_macros)]
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}

View File

@ -0,0 +1,369 @@
#![no_std]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
use core::cell::Cell;
use core::mem::{self, MaybeUninit};
use core::sync::atomic::{AtomicBool, Ordering};
use embassy::blocking_mutex::CriticalSectionMutex;
use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request};
use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError};
use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder};
/// This should be used as `device_class` when building the `UsbDevice`.
pub const USB_CLASS_CDC: u8 = 0x02;
const USB_CLASS_CDC_DATA: u8 = 0x0a;
const CDC_SUBCLASS_ACM: u8 = 0x02;
const CDC_PROTOCOL_NONE: u8 = 0x00;
const CS_INTERFACE: u8 = 0x24;
const CDC_TYPE_HEADER: u8 = 0x00;
const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
const CDC_TYPE_ACM: u8 = 0x02;
const CDC_TYPE_UNION: u8 = 0x06;
const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
#[allow(unused)]
const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
const REQ_SET_LINE_CODING: u8 = 0x20;
const REQ_GET_LINE_CODING: u8 = 0x21;
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
pub struct State<'a> {
control: MaybeUninit<Control<'a>>,
shared: ControlShared,
}
impl<'a> State<'a> {
pub fn new() -> Self {
Self {
control: MaybeUninit::uninit(),
shared: Default::default(),
}
}
}
/// Packet level implementation of a CDC-ACM serial port.
///
/// This class can be used directly and it has the least overhead due to directly reading and
/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
/// port. The following constraints must be followed if you use this class directly:
///
/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes.
/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes.
/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
/// can be sent if there is no other data to send. This is because USB bulk transactions must be
/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
pub struct CdcAcmClass<'d, D: Driver<'d>> {
_comm_ep: D::EndpointIn,
_data_if: InterfaceNumber,
read_ep: D::EndpointOut,
write_ep: D::EndpointIn,
control: &'d ControlShared,
}
struct Control<'a> {
shared: &'a ControlShared,
}
/// Shared data between Control and CdcAcmClass
struct ControlShared {
line_coding: CriticalSectionMutex<Cell<LineCoding>>,
dtr: AtomicBool,
rts: AtomicBool,
}
impl Default for ControlShared {
fn default() -> Self {
ControlShared {
dtr: AtomicBool::new(false),
rts: AtomicBool::new(false),
line_coding: CriticalSectionMutex::new(Cell::new(LineCoding {
stop_bits: StopBits::One,
data_bits: 8,
parity_type: ParityType::None,
data_rate: 8_000,
})),
}
}
}
impl<'a> Control<'a> {
fn shared(&mut self) -> &'a ControlShared {
self.shared
}
}
impl<'d> ControlHandler for Control<'d> {
fn reset(&mut self) {
let shared = self.shared();
shared.line_coding.lock(|x| x.set(LineCoding::default()));
shared.dtr.store(false, Ordering::Relaxed);
shared.rts.store(false, Ordering::Relaxed);
}
fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse {
match req.request {
REQ_SEND_ENCAPSULATED_COMMAND => {
// We don't actually support encapsulated commands but pretend we do for standards
// compatibility.
OutResponse::Accepted
}
REQ_SET_LINE_CODING if data.len() >= 7 => {
let coding = LineCoding {
data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()),
stop_bits: data[4].into(),
parity_type: data[5].into(),
data_bits: data[6],
};
self.shared().line_coding.lock(|x| x.set(coding));
debug!("Set line coding to: {:?}", coding);
OutResponse::Accepted
}
REQ_SET_CONTROL_LINE_STATE => {
let dtr = (req.value & 0x0001) != 0;
let rts = (req.value & 0x0002) != 0;
let shared = self.shared();
shared.dtr.store(dtr, Ordering::Relaxed);
shared.rts.store(rts, Ordering::Relaxed);
debug!("Set dtr {}, rts {}", dtr, rts);
OutResponse::Accepted
}
_ => OutResponse::Rejected,
}
}
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
match req.request {
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
REQ_GET_LINE_CODING if req.length == 7 => {
debug!("Sending line coding");
let coding = self.shared().line_coding.lock(|x| x.get());
assert!(buf.len() >= 7);
buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes());
buf[4] = coding.stop_bits as u8;
buf[5] = coding.parity_type as u8;
buf[6] = coding.data_bits;
InResponse::Accepted(&buf[0..7])
}
_ => InResponse::Rejected,
}
}
}
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 UsbDeviceBuilder<'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 comm_if = builder.alloc_interface_with_handler(control);
let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255);
let data_if = builder.alloc_interface();
let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size);
let write_ep = builder.alloc_bulk_endpoint_in(max_packet_size);
builder.config_descriptor.iad(
comm_if,
2,
USB_CLASS_CDC,
CDC_SUBCLASS_ACM,
CDC_PROTOCOL_NONE,
);
builder.config_descriptor.interface(
comm_if,
USB_CLASS_CDC,
CDC_SUBCLASS_ACM,
CDC_PROTOCOL_NONE,
);
builder.config_descriptor.write(
CS_INTERFACE,
&[
CDC_TYPE_HEADER, // bDescriptorSubtype
0x10,
0x01, // bcdCDC (1.10)
],
);
builder.config_descriptor.write(
CS_INTERFACE,
&[
CDC_TYPE_ACM, // bDescriptorSubtype
0x00, // bmCapabilities
],
);
builder.config_descriptor.write(
CS_INTERFACE,
&[
CDC_TYPE_UNION, // bDescriptorSubtype
comm_if.into(), // bControlInterface
data_if.into(), // bSubordinateInterface
],
);
builder.config_descriptor.write(
CS_INTERFACE,
&[
CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
0x00, // bmCapabilities
data_if.into(), // bDataInterface
],
);
builder.config_descriptor.endpoint(comm_ep.info());
builder
.config_descriptor
.interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00);
builder.config_descriptor.endpoint(write_ep.info());
builder.config_descriptor.endpoint(read_ep.info());
CdcAcmClass {
_comm_ep: comm_ep,
_data_if: data_if,
read_ep,
write_ep,
control: control_shared,
}
}
/// Gets the maximum packet size in bytes.
pub fn max_packet_size(&self) -> u16 {
// The size is the same for both endpoints.
self.read_ep.info().max_packet_size
}
/// Gets the current line coding. The line coding contains information that's mainly relevant
/// for USB to UART serial port emulators, and can be ignored if not relevant.
pub fn line_coding(&self) -> LineCoding {
self.control.line_coding.lock(|x| x.get())
}
/// Gets the DTR (data terminal ready) state
pub fn dtr(&self) -> bool {
self.control.dtr.load(Ordering::Relaxed)
}
/// Gets the RTS (request to send) state
pub fn rts(&self) -> bool {
self.control.rts.load(Ordering::Relaxed)
}
/// Writes a single packet into the IN endpoint.
pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), WriteError> {
self.write_ep.write(data).await
}
/// Reads a single packet from the OUT endpoint.
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
#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)]
pub enum StopBits {
/// 1 stop bit
One = 0,
/// 1.5 stop bits
OnePointFive = 1,
/// 2 stop bits
Two = 2,
}
impl From<u8> for StopBits {
fn from(value: u8) -> Self {
if value <= 2 {
unsafe { mem::transmute(value) }
} else {
StopBits::One
}
}
}
/// Parity for LineCoding
#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)]
pub enum ParityType {
None = 0,
Odd = 1,
Event = 2,
Mark = 3,
Space = 4,
}
impl From<u8> for ParityType {
fn from(value: u8) -> Self {
if value <= 4 {
unsafe { mem::transmute(value) }
} else {
ParityType::None
}
}
}
/// Line coding parameters
///
/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
/// be ignored if you don't plan to interface with a physical UART.
#[derive(Clone, Copy, defmt::Format)]
pub struct LineCoding {
stop_bits: StopBits,
data_bits: u8,
parity_type: ParityType,
data_rate: u32,
}
impl LineCoding {
/// Gets the number of stop bits for UART communication.
pub fn stop_bits(&self) -> StopBits {
self.stop_bits
}
/// Gets the number of data bits for UART communication.
pub fn data_bits(&self) -> u8 {
self.data_bits
}
/// Gets the parity type for UART communication.
pub fn parity_type(&self) -> ParityType {
self.parity_type
}
/// Gets the data rate in bits per second for UART communication.
pub fn data_rate(&self) -> u32 {
self.data_rate
}
}
impl Default for LineCoding {
fn default() -> Self {
LineCoding {
stop_bits: StopBits::One,
data_bits: 8,
parity_type: ParityType::None,
data_rate: 8_000,
}
}
}

19
embassy-usb/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "embassy-usb"
version = "0.1.0"
edition = "2021"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-v$VERSION/embassy-usb/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/master/embassy-usb/src/"
features = ["defmt"]
flavors = [
{ name = "default", target = "thumbv7em-none-eabihf" },
]
[dependencies]
embassy = { version = "0.1.0", path = "../embassy" }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
heapless = "0.7.10"

386
embassy-usb/src/builder.rs Normal file
View File

@ -0,0 +1,386 @@
use heapless::Vec;
use super::control::ControlHandler;
use super::descriptor::{BosWriter, DescriptorWriter};
use super::driver::{Driver, EndpointAllocError};
use super::types::*;
use super::UsbDevice;
use super::MAX_INTERFACE_COUNT;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Config<'a> {
pub(crate) vendor_id: u16,
pub(crate) product_id: u16,
/// Device class code assigned by USB.org. Set to `0xff` for vendor-specific
/// devices that do not conform to any class.
///
/// Default: `0x00` (class code specified by interfaces)
pub device_class: u8,
/// Device sub-class code. Depends on class.
///
/// Default: `0x00`
pub device_sub_class: u8,
/// Device protocol code. Depends on class and sub-class.
///
/// Default: `0x00`
pub device_protocol: u8,
/// Device release version in BCD.
///
/// Default: `0x0010` ("0.1")
pub device_release: u16,
/// Maximum packet size in bytes for the control endpoint 0.
///
/// Valid values are 8, 16, 32 and 64. There's generally no need to change this from the default
/// value of 8 bytes unless a class uses control transfers for sending large amounts of data, in
/// which case using a larger packet size may be more efficient.
///
/// Default: 8 bytes
pub max_packet_size_0: u8,
/// Manufacturer name string descriptor.
///
/// Default: (none)
pub manufacturer: Option<&'a str>,
/// Product name string descriptor.
///
/// Default: (none)
pub product: Option<&'a str>,
/// Serial number string descriptor.
///
/// Default: (none)
pub serial_number: Option<&'a str>,
/// Whether the device supports remotely waking up the host is requested.
///
/// Default: `false`
pub supports_remote_wakeup: bool,
/// Configures the device as a composite device with interface association descriptors.
///
/// If set to `true`, the following fields should have the given values:
///
/// - `device_class` = `0xEF`
/// - `device_sub_class` = `0x02`
/// - `device_protocol` = `0x01`
pub composite_with_iads: bool,
/// Whether the device has its own power source.
///
/// This should be set to `true` even if the device is sometimes self-powered and may not
/// always draw power from the USB bus.
///
/// Default: `false`
///
/// See also: `max_power`
pub self_powered: bool,
/// Maximum current drawn from the USB bus by the device, in milliamps.
///
/// The default is 100 mA. If your device always uses an external power source and never draws
/// power from the USB bus, this can be set to 0.
///
/// See also: `self_powered`
///
/// Default: 100mA
/// Max: 500mA
pub max_power: u16,
}
impl<'a> Config<'a> {
pub fn new(vid: u16, pid: u16) -> Self {
Self {
device_class: 0x00,
device_sub_class: 0x00,
device_protocol: 0x00,
max_packet_size_0: 8,
vendor_id: vid,
product_id: pid,
device_release: 0x0010,
manufacturer: None,
product: None,
serial_number: None,
self_powered: false,
supports_remote_wakeup: false,
composite_with_iads: false,
max_power: 100,
}
}
}
/// Used to build new [`UsbDevice`]s.
pub struct UsbDeviceBuilder<'d, D: Driver<'d>> {
config: Config<'d>,
interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>,
control_buf: &'d mut [u8],
driver: D,
next_interface_number: u8,
next_string_index: u8,
// TODO make not pub?
pub device_descriptor: DescriptorWriter<'d>,
pub config_descriptor: DescriptorWriter<'d>,
pub bos_descriptor: BosWriter<'d>,
}
impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> {
/// Creates a builder for constructing a new [`UsbDevice`].
///
/// `control_buf` is a buffer used for USB control request data. It should be sized
/// large enough for the length of the largest control request (in or out)
/// anticipated by any class added to the device.
pub fn new(
driver: D,
config: Config<'d>,
device_descriptor_buf: &'d mut [u8],
config_descriptor_buf: &'d mut [u8],
bos_descriptor_buf: &'d mut [u8],
control_buf: &'d mut [u8],
) -> Self {
// Magic values specified in USB-IF ECN on IADs.
if config.composite_with_iads
&& (config.device_class != 0xEF
|| config.device_sub_class != 0x02
|| config.device_protocol != 0x01)
{
panic!("if composite_with_iads is set, you must set device_class = 0xEF, device_sub_class = 0x02, device_protocol = 0x01");
}
if config.max_power > 500 {
panic!("The maximum allowed value for `max_power` is 500mA");
}
match config.max_packet_size_0 {
8 | 16 | 32 | 64 => {}
_ => panic!("invalid max_packet_size_0, the allowed values are 8, 16, 32 or 64"),
}
let mut device_descriptor = DescriptorWriter::new(device_descriptor_buf);
let mut config_descriptor = DescriptorWriter::new(config_descriptor_buf);
let mut bos_descriptor = BosWriter::new(DescriptorWriter::new(bos_descriptor_buf));
device_descriptor.device(&config);
config_descriptor.configuration(&config);
bos_descriptor.bos();
UsbDeviceBuilder {
driver,
config,
interfaces: Vec::new(),
control_buf,
next_interface_number: 0,
next_string_index: 4,
device_descriptor,
config_descriptor,
bos_descriptor,
}
}
/// Creates the [`UsbDevice`] instance with the configuration in this builder.
pub async fn build(mut self) -> UsbDevice<'d, D> {
self.config_descriptor.end_configuration();
self.bos_descriptor.end_bos();
UsbDevice::build(
self.driver,
self.config,
self.device_descriptor.into_buf(),
self.config_descriptor.into_buf(),
self.bos_descriptor.writer.into_buf(),
self.interfaces,
self.control_buf,
)
.await
}
/// Allocates a new interface number.
pub fn alloc_interface(&mut self) -> InterfaceNumber {
let number = self.next_interface_number;
self.next_interface_number += 1;
InterfaceNumber::new(number)
}
/// Returns the size of the control request data buffer. Can be used by
/// classes to validate the buffer is large enough for their needs.
pub fn control_buf_len(&self) -> usize {
self.control_buf.len()
}
/// Allocates a new interface number, with a handler that will be called
/// for all the control requests directed to it.
pub fn alloc_interface_with_handler(
&mut self,
handler: &'d mut dyn ControlHandler,
) -> InterfaceNumber {
let number = self.next_interface_number;
self.next_interface_number += 1;
if self.interfaces.push((number, handler)).is_err() {
panic!("max class count reached")
}
InterfaceNumber::new(number)
}
/// Allocates a new string index.
pub fn alloc_string(&mut self) -> StringIndex {
let index = self.next_string_index;
self.next_string_index += 1;
StringIndex::new(index)
}
/// Allocates an in endpoint.
///
/// This directly delegates to [`Driver::alloc_endpoint_in`], so see that method for details. In most
/// cases classes should call the endpoint type specific methods instead.
pub fn alloc_endpoint_in(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<D::EndpointIn, EndpointAllocError> {
self.driver
.alloc_endpoint_in(ep_addr, ep_type, max_packet_size, interval)
}
/// Allocates an out endpoint.
///
/// This directly delegates to [`Driver::alloc_endpoint_out`], so see that method for details. In most
/// cases classes should call the endpoint type specific methods instead.
pub fn alloc_endpoint_out(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<D::EndpointOut, EndpointAllocError> {
self.driver
.alloc_endpoint_out(ep_addr, ep_type, max_packet_size, interval)
}
/// Allocates a control in endpoint.
///
/// This crate implements the control state machine only for endpoint 0. If classes want to
/// support control requests in other endpoints, the state machine must be implemented manually.
/// This should rarely be needed by classes.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn alloc_control_endpoint_in(&mut self, max_packet_size: u16) -> D::EndpointIn {
self.alloc_endpoint_in(None, EndpointType::Control, max_packet_size, 0)
.expect("alloc_ep failed")
}
/// Allocates a control out endpoint.
///
/// This crate implements the control state machine only for endpoint 0. If classes want to
/// support control requests in other endpoints, the state machine must be implemented manually.
/// This should rarely be needed by classes.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn alloc_control_pipe(&mut self, max_packet_size: u16) -> D::ControlPipe {
self.driver
.alloc_control_pipe(max_packet_size)
.expect("alloc_control_pipe failed")
}
/// Allocates a bulk in endpoint.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn alloc_bulk_endpoint_in(&mut self, max_packet_size: u16) -> D::EndpointIn {
self.alloc_endpoint_in(None, EndpointType::Bulk, max_packet_size, 0)
.expect("alloc_ep failed")
}
/// Allocates a bulk out endpoint.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn alloc_bulk_endpoint_out(&mut self, max_packet_size: u16) -> D::EndpointOut {
self.alloc_endpoint_out(None, EndpointType::Bulk, max_packet_size, 0)
.expect("alloc_ep failed")
}
/// Allocates a bulk in endpoint.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn alloc_interrupt_endpoint_in(
&mut self,
max_packet_size: u16,
interval: u8,
) -> D::EndpointIn {
self.alloc_endpoint_in(None, EndpointType::Interrupt, max_packet_size, interval)
.expect("alloc_ep failed")
}
/// Allocates a bulk in endpoint.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn alloc_interrupt_endpoint_out(
&mut self,
max_packet_size: u16,
interval: u8,
) -> D::EndpointOut {
self.alloc_endpoint_out(None, EndpointType::Interrupt, max_packet_size, interval)
.expect("alloc_ep failed")
}
}

330
embassy-usb/src/control.rs Normal file
View File

@ -0,0 +1,330 @@
use core::mem;
use crate::descriptor::DescriptorWriter;
use crate::driver::{self, ReadError};
use crate::DEFAULT_ALTERNATE_SETTING;
use super::types::*;
/// Control request type.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RequestType {
/// Request is a USB standard request. Usually handled by
/// [`UsbDevice`](crate::device::UsbDevice).
Standard = 0,
/// Request is intended for a USB class.
Class = 1,
/// Request is vendor-specific.
Vendor = 2,
/// Reserved.
Reserved = 3,
}
/// Control request recipient.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Recipient {
/// Request is intended for the entire device.
Device = 0,
/// Request is intended for an interface. Generally, the `index` field of the request specifies
/// the interface number.
Interface = 1,
/// Request is intended for an endpoint. Generally, the `index` field of the request specifies
/// the endpoint address.
Endpoint = 2,
/// None of the above.
Other = 3,
/// Reserved.
Reserved = 4,
}
/// A control request read from a SETUP packet.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Request {
/// Direction of the request.
pub direction: UsbDirection,
/// Type of the request.
pub request_type: RequestType,
/// Recipient of the request.
pub recipient: Recipient,
/// Request code. The meaning of the value depends on the previous fields.
pub request: u8,
/// Request value. The meaning of the value depends on the previous fields.
pub value: u16,
/// Request index. The meaning of the value depends on the previous fields.
pub index: u16,
/// Length of the DATA stage. For control OUT transfers this is the exact length of the data the
/// host sent. For control IN transfers this is the maximum length of data the device should
/// return.
pub length: u16,
}
impl Request {
/// Standard USB control request Get Status
pub const GET_STATUS: u8 = 0;
/// Standard USB control request Clear Feature
pub const CLEAR_FEATURE: u8 = 1;
/// Standard USB control request Set Feature
pub const SET_FEATURE: u8 = 3;
/// Standard USB control request Set Address
pub const SET_ADDRESS: u8 = 5;
/// Standard USB control request Get Descriptor
pub const GET_DESCRIPTOR: u8 = 6;
/// Standard USB control request Set Descriptor
pub const SET_DESCRIPTOR: u8 = 7;
/// Standard USB control request Get Configuration
pub const GET_CONFIGURATION: u8 = 8;
/// Standard USB control request Set Configuration
pub const SET_CONFIGURATION: u8 = 9;
/// Standard USB control request Get Interface
pub const GET_INTERFACE: u8 = 10;
/// Standard USB control request Set Interface
pub const SET_INTERFACE: u8 = 11;
/// Standard USB control request Synch Frame
pub const SYNCH_FRAME: u8 = 12;
/// Standard USB feature Endpoint Halt for Set/Clear Feature
pub const FEATURE_ENDPOINT_HALT: u16 = 0;
/// Standard USB feature Device Remote Wakeup for Set/Clear Feature
pub const FEATURE_DEVICE_REMOTE_WAKEUP: u16 = 1;
/// Parses a USB control request from a byte array.
pub fn parse(buf: &[u8; 8]) -> Request {
let rt = buf[0];
let recipient = rt & 0b11111;
Request {
direction: rt.into(),
request_type: unsafe { mem::transmute((rt >> 5) & 0b11) },
recipient: if recipient <= 3 {
unsafe { mem::transmute(recipient) }
} else {
Recipient::Reserved
},
request: buf[1],
value: (buf[2] as u16) | ((buf[3] as u16) << 8),
index: (buf[4] as u16) | ((buf[5] as u16) << 8),
length: (buf[6] as u16) | ((buf[7] as u16) << 8),
}
}
/// Gets the descriptor type and index from the value field of a GET_DESCRIPTOR request.
pub fn descriptor_type_index(&self) -> (u8, u8) {
((self.value >> 8) as u8, self.value as u8)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum OutResponse {
Accepted,
Rejected,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InResponse<'a> {
Accepted(&'a [u8]),
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 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 to `resp`, then return `InResponse::Accepted(len)`
/// with the length of the response.
///
/// # 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
}
fn set_interface(&mut self, alternate_setting: u16) -> OutResponse {
if alternate_setting == u16::from(DEFAULT_ALTERNATE_SETTING) {
OutResponse::Accepted
} else {
OutResponse::Rejected
}
}
fn get_interface<'a>(&'a mut self, buf: &'a mut [u8]) -> InResponse<'a> {
buf[0] = DEFAULT_ALTERNATE_SETTING;
InResponse::Accepted(&buf[0..1])
}
fn get_status<'a>(&'a mut self, buf: &'a mut [u8]) -> InResponse {
let status: u16 = 0;
buf[0..2].copy_from_slice(&status.to_le_bytes());
InResponse::Accepted(&buf[0..2])
}
}
/// Typestate representing a ControlPipe in the DATA IN stage
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct DataInStage {
pub(crate) length: usize,
}
/// Typestate representing a ControlPipe in the DATA OUT stage
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct DataOutStage {
length: usize,
}
/// Typestate representing a ControlPipe in the STATUS stage
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct StatusStage {}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) enum Setup {
DataIn(Request, DataInStage),
DataOut(Request, DataOutStage),
}
pub(crate) struct ControlPipe<C: driver::ControlPipe> {
control: C,
}
impl<C: driver::ControlPipe> ControlPipe<C> {
pub(crate) fn new(control: C) -> Self {
ControlPipe { control }
}
pub(crate) async fn setup(&mut self) -> Setup {
let req = self.control.setup().await;
trace!("control request: {:02x}", req);
match (req.direction, req.length) {
(UsbDirection::Out, n) => Setup::DataOut(
req,
DataOutStage {
length: usize::from(n),
},
),
(UsbDirection::In, n) => Setup::DataIn(
req,
DataInStage {
length: usize::from(n),
},
),
}
}
pub(crate) async fn data_out<'a>(
&mut self,
buf: &'a mut [u8],
stage: DataOutStage,
) -> Result<(&'a [u8], StatusStage), ReadError> {
if stage.length == 0 {
Ok((&[], StatusStage {}))
} else {
let req_length = stage.length;
let max_packet_size = self.control.max_packet_size();
let mut total = 0;
for chunk in buf.chunks_mut(max_packet_size) {
let size = self.control.data_out(chunk).await?;
total += size;
if size < max_packet_size || total == req_length {
break;
}
}
let res = &buf[0..total];
#[cfg(feature = "defmt")]
trace!(" control out data: {:02x}", buf);
#[cfg(not(feature = "defmt"))]
trace!(" control out data: {:02x?}", buf);
Ok((res, StatusStage {}))
}
}
pub(crate) async fn accept_in(&mut self, buf: &[u8], stage: DataInStage) {
#[cfg(feature = "defmt")]
trace!(" control in accept {:02x}", buf);
#[cfg(not(feature = "defmt"))]
trace!(" control in accept {:02x?}", buf);
let req_len = stage.length;
let len = buf.len().min(req_len);
let max_packet_size = self.control.max_packet_size();
let need_zlp = len != req_len && (len % usize::from(max_packet_size)) == 0;
let mut chunks = buf[0..len]
.chunks(max_packet_size)
.chain(need_zlp.then(|| -> &[u8] { &[] }));
while let Some(chunk) = chunks.next() {
match self.control.data_in(chunk, chunks.size_hint().0 == 0).await {
Ok(()) => {}
Err(e) => {
warn!("control accept_in failed: {:?}", e);
return;
}
}
}
}
pub(crate) async fn accept_in_writer(
&mut self,
req: Request,
stage: DataInStage,
f: impl FnOnce(&mut DescriptorWriter),
) {
let mut buf = [0; 256];
let mut w = DescriptorWriter::new(&mut buf);
f(&mut w);
let pos = w.position().min(usize::from(req.length));
self.accept_in(&buf[..pos], stage).await
}
pub(crate) fn accept(&mut self, _: StatusStage) {
trace!(" control accept");
self.control.accept();
}
pub(crate) fn reject(&mut self) {
trace!(" control reject");
self.control.reject();
}
}

View File

@ -0,0 +1,369 @@
use super::builder::Config;
use super::{types::*, CONFIGURATION_VALUE, DEFAULT_ALTERNATE_SETTING};
/// Standard descriptor types
#[allow(missing_docs)]
pub mod descriptor_type {
pub const DEVICE: u8 = 1;
pub const CONFIGURATION: u8 = 2;
pub const STRING: u8 = 3;
pub const INTERFACE: u8 = 4;
pub const ENDPOINT: u8 = 5;
pub const IAD: u8 = 11;
pub const BOS: u8 = 15;
pub const CAPABILITY: u8 = 16;
}
/// String descriptor language IDs.
pub mod lang_id {
/// English (US)
///
/// Recommended for use as the first language ID for compatibility.
pub const ENGLISH_US: u16 = 0x0409;
}
/// Standard capability descriptor types
#[allow(missing_docs)]
pub mod capability_type {
pub const WIRELESS_USB: u8 = 1;
pub const USB_2_0_EXTENSION: u8 = 2;
pub const SS_USB_DEVICE: u8 = 3;
pub const CONTAINER_ID: u8 = 4;
pub const PLATFORM: u8 = 5;
}
/// A writer for USB descriptors.
pub struct DescriptorWriter<'a> {
buf: &'a mut [u8],
position: usize,
num_interfaces_mark: Option<usize>,
num_endpoints_mark: Option<usize>,
write_iads: bool,
}
impl<'a> DescriptorWriter<'a> {
pub(crate) fn new(buf: &'a mut [u8]) -> Self {
DescriptorWriter {
buf,
position: 0,
num_interfaces_mark: None,
num_endpoints_mark: None,
write_iads: false,
}
}
pub fn into_buf(self) -> &'a mut [u8] {
&mut self.buf[..self.position]
}
/// Gets the current position in the buffer, i.e. the number of bytes written so far.
pub fn position(&self) -> usize {
self.position
}
/// Writes an arbitrary (usually class-specific) descriptor.
pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) {
let length = descriptor.len();
if (self.position + 2 + length) > self.buf.len() || (length + 2) > 255 {
panic!("Descriptor buffer full");
}
self.buf[self.position] = (length + 2) as u8;
self.buf[self.position + 1] = descriptor_type;
let start = self.position + 2;
self.buf[start..start + length].copy_from_slice(descriptor);
self.position = start + length;
}
pub(crate) fn device(&mut self, config: &Config) {
self.write(
descriptor_type::DEVICE,
&[
0x10,
0x02, // bcdUSB 2.1
config.device_class, // bDeviceClass
config.device_sub_class, // bDeviceSubClass
config.device_protocol, // bDeviceProtocol
config.max_packet_size_0, // bMaxPacketSize0
config.vendor_id as u8,
(config.vendor_id >> 8) as u8, // idVendor
config.product_id as u8,
(config.product_id >> 8) as u8, // idProduct
config.device_release as u8,
(config.device_release >> 8) as u8, // bcdDevice
config.manufacturer.map_or(0, |_| 1), // iManufacturer
config.product.map_or(0, |_| 2), // iProduct
config.serial_number.map_or(0, |_| 3), // iSerialNumber
1, // bNumConfigurations
],
)
}
pub(crate) fn configuration(&mut self, config: &Config) {
self.num_interfaces_mark = Some(self.position + 4);
self.write_iads = config.composite_with_iads;
self.write(
descriptor_type::CONFIGURATION,
&[
0,
0, // wTotalLength
0, // bNumInterfaces
CONFIGURATION_VALUE, // bConfigurationValue
0, // iConfiguration
0x80 | if config.self_powered { 0x40 } else { 0x00 }
| if config.supports_remote_wakeup {
0x20
} else {
0x00
}, // bmAttributes
(config.max_power / 2) as u8, // bMaxPower
],
)
}
#[allow(unused)]
pub(crate) fn end_class(&mut self) {
self.num_endpoints_mark = None;
}
pub(crate) fn end_configuration(&mut self) {
let position = self.position as u16;
self.buf[2..4].copy_from_slice(&position.to_le_bytes());
}
/// Writes a interface association descriptor. Call from `UsbClass::get_configuration_descriptors`
/// before writing the USB class or function's interface descriptors if your class has more than
/// one interface and wants to play nicely with composite devices on Windows. If the USB device
/// hosting the class was not configured as composite with IADs enabled, calling this function
/// does nothing, so it is safe to call from libraries.
///
/// # Arguments
///
/// * `first_interface` - Number of the function's first interface, previously allocated with
/// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface).
/// * `interface_count` - Number of interfaces in the function.
/// * `function_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `function_sub_class` - Sub-class code. Depends on class.
/// * `function_protocol` - Protocol code. Depends on class and sub-class.
pub fn iad(
&mut self,
first_interface: InterfaceNumber,
interface_count: u8,
function_class: u8,
function_sub_class: u8,
function_protocol: u8,
) {
if !self.write_iads {
return;
}
self.write(
descriptor_type::IAD,
&[
first_interface.into(), // bFirstInterface
interface_count, // bInterfaceCount
function_class,
function_sub_class,
function_protocol,
0,
],
);
}
/// Writes a interface descriptor.
///
/// # Arguments
///
/// * `number` - Interface number previously allocated with
/// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface).
/// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `interface_sub_class` - Sub-class code. Depends on class.
/// * `interface_protocol` - Protocol code. Depends on class and sub-class.
pub fn interface(
&mut self,
number: InterfaceNumber,
interface_class: u8,
interface_sub_class: u8,
interface_protocol: u8,
) {
self.interface_alt(
number,
DEFAULT_ALTERNATE_SETTING,
interface_class,
interface_sub_class,
interface_protocol,
None,
)
}
/// Writes a interface descriptor with a specific alternate setting and
/// interface string identifier.
///
/// # Arguments
///
/// * `number` - Interface number previously allocated with
/// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface).
/// * `alternate_setting` - Number of the alternate setting
/// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `interface_sub_class` - Sub-class code. Depends on class.
/// * `interface_protocol` - Protocol code. Depends on class and sub-class.
/// * `interface_string` - Index of string descriptor describing this interface
pub fn interface_alt(
&mut self,
number: InterfaceNumber,
alternate_setting: u8,
interface_class: u8,
interface_sub_class: u8,
interface_protocol: u8,
interface_string: Option<StringIndex>,
) {
if alternate_setting == DEFAULT_ALTERNATE_SETTING {
match self.num_interfaces_mark {
Some(mark) => self.buf[mark] += 1,
None => {
panic!("you can only call `interface/interface_alt` after `configuration`.")
}
};
}
let str_index = interface_string.map_or(0, Into::into);
self.num_endpoints_mark = Some(self.position + 4);
self.write(
descriptor_type::INTERFACE,
&[
number.into(), // bInterfaceNumber
alternate_setting, // bAlternateSetting
0, // bNumEndpoints
interface_class, // bInterfaceClass
interface_sub_class, // bInterfaceSubClass
interface_protocol, // bInterfaceProtocol
str_index, // iInterface
],
);
}
/// Writes an endpoint descriptor.
///
/// # Arguments
///
/// * `endpoint` - Endpoint previously allocated with
/// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder).
pub fn endpoint(&mut self, endpoint: &EndpointInfo) {
match self.num_endpoints_mark {
Some(mark) => self.buf[mark] += 1,
None => panic!("you can only call `endpoint` after `interface/interface_alt`."),
};
self.write(
descriptor_type::ENDPOINT,
&[
endpoint.addr.into(), // bEndpointAddress
endpoint.ep_type as u8, // bmAttributes
endpoint.max_packet_size as u8,
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
endpoint.interval, // bInterval
],
);
}
/// Writes a string descriptor.
pub(crate) fn string(&mut self, string: &str) {
let mut pos = self.position;
if pos + 2 > self.buf.len() {
panic!("Descriptor buffer full");
}
self.buf[pos] = 0; // length placeholder
self.buf[pos + 1] = descriptor_type::STRING;
pos += 2;
for c in string.encode_utf16() {
if pos >= self.buf.len() {
panic!("Descriptor buffer full");
}
self.buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes());
pos += 2;
}
self.buf[self.position] = (pos - self.position) as u8;
self.position = pos;
}
}
/// A writer for Binary Object Store descriptor.
pub struct BosWriter<'a> {
pub(crate) writer: DescriptorWriter<'a>,
num_caps_mark: Option<usize>,
}
impl<'a> BosWriter<'a> {
pub(crate) fn new(writer: DescriptorWriter<'a>) -> Self {
Self {
writer: writer,
num_caps_mark: None,
}
}
pub(crate) fn bos(&mut self) {
self.num_caps_mark = Some(self.writer.position + 4);
self.writer.write(
descriptor_type::BOS,
&[
0x00, 0x00, // wTotalLength
0x00, // bNumDeviceCaps
],
);
self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]);
}
/// Writes capability descriptor to a BOS
///
/// # Arguments
///
/// * `capability_type` - Type of a capability
/// * `data` - Binary data of the descriptor
pub fn capability(&mut self, capability_type: u8, data: &[u8]) {
match self.num_caps_mark {
Some(mark) => self.writer.buf[mark] += 1,
None => panic!("called `capability` not between `bos` and `end_bos`."),
}
let mut start = self.writer.position;
let blen = data.len();
if (start + blen + 3) > self.writer.buf.len() || (blen + 3) > 255 {
panic!("Descriptor buffer full");
}
self.writer.buf[start] = (blen + 3) as u8;
self.writer.buf[start + 1] = descriptor_type::CAPABILITY;
self.writer.buf[start + 2] = capability_type;
start += 3;
self.writer.buf[start..start + blen].copy_from_slice(data);
self.writer.position = start + blen;
}
pub(crate) fn end_bos(&mut self) {
self.num_caps_mark = None;
let position = self.writer.position as u16;
self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes());
}
}

239
embassy-usb/src/driver.rs Normal file
View File

@ -0,0 +1,239 @@
use core::future::Future;
use crate::control::Request;
use super::types::*;
/// Driver for a specific USB peripheral. Implement this to add support for a new hardware
/// platform.
pub trait Driver<'a> {
type EndpointOut: EndpointOut + 'a;
type EndpointIn: EndpointIn + 'a;
type ControlPipe: ControlPipe + 'a;
type Bus: Bus + 'a;
type EnableFuture: Future<Output = Self::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
/// [`enable`](UsbBus::enable) 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.
/// * `max_packet_size` - Maximum packet size in bytes.
/// * `interval` - Polling interval parameter for interrupt endpoints.
fn alloc_endpoint_out(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<Self::EndpointOut, EndpointAllocError>;
fn alloc_endpoint_in(
&mut self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<Self::EndpointIn, EndpointAllocError>;
fn alloc_control_pipe(
&mut self,
max_packet_size: u16,
) -> Result<Self::ControlPipe, EndpointAllocError>;
/// Enables and initializes the USB peripheral. Soon after enabling the device will be reset, so
/// there is no need to perform a USB reset in this method.
fn enable(self) -> Self::EnableFuture;
/// 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;
}
pub trait Bus {
type PollFuture<'a>: Future<Output = Event> + 'a
where
Self: 'a;
fn poll<'a>(&'a mut self) -> Self::PollFuture<'a>;
/// Called when the host resets the device. This will be soon called after
/// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Reset`]. This method should
/// reset the state of all endpoints and peripheral flags back to a state suitable for
/// enumeration, as well as ensure that all endpoints previously allocated with alloc_ep are
/// initialized as specified.
fn reset(&mut self);
/// 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);
/// Gets whether the STALL condition is set for an endpoint. Only used during control transfers.
fn is_stalled(&mut self, ep_addr: EndpointAddress) -> bool;
/// Causes the USB peripheral to enter USB suspend mode, lowering power consumption and
/// preparing to detect a USB wakeup event. This will be called after
/// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Suspend`]. The device will
/// continue be polled, and it shall return a value other than `Suspend` from `poll` when it no
/// longer detects the suspend condition.
fn suspend(&mut self);
/// Resumes from suspend mode. This may only be called after the peripheral has been previously
/// suspended.
fn resume(&mut self);
/// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the
/// device.
///
/// The default implementation just returns `Unsupported`.
///
/// # Errors
///
/// * [`Unsupported`](crate::UsbError::Unsupported) - This UsbBus implementation doesn't support
/// simulating a disconnect or it has not been enabled at creation time.
fn force_reset(&mut self) -> Result<(), Unsupported> {
Err(Unsupported)
}
}
pub trait Endpoint {
type WaitEnabledFuture<'a>: Future<Output = ()> + 'a
where
Self: 'a;
/// Get the endpoint address
fn info(&self) -> &EndpointInfo;
/// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it
/// should be prepared to receive data again.
fn set_stalled(&self, stalled: bool);
/// 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?
}
pub trait EndpointOut: Endpoint {
type ReadFuture<'a>: Future<Output = Result<usize, ReadError>> + 'a
where
Self: 'a;
/// Reads a single packet of data from the endpoint, and returns the actual length of
/// the packet.
///
/// This should also clear any NAK flags and prepare the endpoint to receive the next packet.
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>;
}
pub trait ControlPipe {
type SetupFuture<'a>: Future<Output = Request> + 'a
where
Self: 'a;
type DataOutFuture<'a>: Future<Output = Result<usize, ReadError>> + 'a
where
Self: 'a;
type DataInFuture<'a>: Future<Output = Result<(), WriteError>> + 'a
where
Self: 'a;
/// Maximum packet size for the control pipe
fn max_packet_size(&self) -> usize;
/// Reads a single setup packet from the endpoint.
fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a>;
/// Reads 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.
fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a>;
/// Sends 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`.
fn data_in<'a>(&'a mut self, data: &'a [u8], last_packet: bool) -> Self::DataInFuture<'a>;
/// Accepts a control request.
///
/// Causes the STATUS packet for the current request to be ACKed.
fn accept(&mut self);
/// Rejects a control request.
///
/// Sets a STALL condition on the pipe to indicate an error.
fn reject(&mut self);
}
pub trait EndpointIn: Endpoint {
type WriteFuture<'a>: Future<Output = Result<(), WriteError>> + 'a
where
Self: 'a;
/// Writes a single packet of data to the endpoint.
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a>;
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Event returned by [`Bus::poll`].
pub enum Event {
/// The USB reset condition has been detected.
Reset,
/// A USB suspend request has been detected or, in the case of self-powered devices, the device
/// has been disconnected from the USB bus.
Suspend,
/// 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,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndpointAllocError;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Operation is unsupported by the driver.
pub struct Unsupported;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Errors returned by [`EndpointIn::write`]
pub enum WriteError {
/// The packet is too long to fit in the
/// transmission buffer. This is generally an error in the class implementation, because the
/// 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)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Errors returned by [`EndpointOut::read`]
pub enum ReadError {
/// The received packet is too long to
/// fit in `buf`. This is generally an error in the class implementation, because the class
/// should use a buffer that is large enough for the `max_packet_size` it specified when
/// allocating the endpoint.
BufferOverflow,
Disabled,
}

225
embassy-usb/src/fmt.rs Normal file
View File

@ -0,0 +1,225 @@
#![macro_use]
#![allow(unused_macros)]
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}

344
embassy-usb/src/lib.rs Normal file
View File

@ -0,0 +1,344 @@
#![no_std]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
mod builder;
pub mod control;
pub mod descriptor;
pub mod driver;
pub mod types;
mod util;
use heapless::Vec;
use self::control::*;
use self::descriptor::*;
use self::driver::{Bus, Driver, Event};
use self::types::*;
use self::util::*;
pub use self::builder::Config;
pub use self::builder::UsbDeviceBuilder;
/// The global state of the USB device.
///
/// In general class traffic is only possible in the `Configured` state.
#[repr(u8)]
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum UsbDeviceState {
/// The USB device has just been created or reset.
Default,
/// The USB device has received an address from the host.
Addressed,
/// The USB device has been configured and is fully functional.
Configured,
/// The USB device has been suspended by the host or it has been unplugged from the USB bus.
Suspend,
}
/// The bConfiguration value for the not configured state.
pub const CONFIGURATION_NONE: u8 = 0;
/// The bConfiguration value for the single configuration supported by this device.
pub const CONFIGURATION_VALUE: u8 = 1;
/// The default value for bAlternateSetting for all interfaces.
pub const DEFAULT_ALTERNATE_SETTING: u8 = 0;
pub const MAX_INTERFACE_COUNT: usize = 4;
pub struct UsbDevice<'d, D: Driver<'d>> {
bus: D::Bus,
control: ControlPipe<D::ControlPipe>,
config: Config<'d>,
device_descriptor: &'d [u8],
config_descriptor: &'d [u8],
bos_descriptor: &'d [u8],
control_buf: &'d mut [u8],
device_state: UsbDeviceState,
remote_wakeup_enabled: bool,
self_powered: bool,
pending_address: u8,
interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>,
}
impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
pub(crate) async fn build(
mut driver: D,
config: Config<'d>,
device_descriptor: &'d [u8],
config_descriptor: &'d [u8],
bos_descriptor: &'d [u8],
interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>,
control_buf: &'d mut [u8],
) -> UsbDevice<'d, D> {
let control = driver
.alloc_control_pipe(config.max_packet_size_0 as u16)
.expect("failed to alloc control endpoint");
// Enable the USB bus.
// This prevent further allocation by consuming the driver.
let bus = driver.enable().await;
Self {
bus,
config,
control: ControlPipe::new(control),
device_descriptor,
config_descriptor,
bos_descriptor,
control_buf,
device_state: UsbDeviceState::Default,
remote_wakeup_enabled: false,
self_powered: false,
pending_address: 0,
interfaces,
}
}
pub async fn run(&mut self) {
loop {
let control_fut = self.control.setup();
let bus_fut = self.bus.poll();
match select(bus_fut, control_fut).await {
Either::Left(evt) => match evt {
Event::Reset => {
trace!("usb: reset");
self.bus.reset();
self.device_state = UsbDeviceState::Default;
self.remote_wakeup_enabled = false;
self.pending_address = 0;
for (_, h) in self.interfaces.iter_mut() {
h.reset();
}
}
Event::Resume => {
trace!("usb: resume");
}
Event::Suspend => {
trace!("usb: suspend");
self.bus.suspend();
self.device_state = UsbDeviceState::Suspend;
}
},
Either::Right(req) => match req {
Setup::DataIn(req, stage) => self.handle_control_in(req, stage).await,
Setup::DataOut(req, stage) => self.handle_control_out(req, stage).await,
},
}
}
}
async fn handle_control_out(&mut self, req: Request, stage: DataOutStage) {
const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16;
const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16;
let (data, stage) = match self.control.data_out(self.control_buf, stage).await {
Ok(data) => data,
Err(_) => {
warn!("usb: failed to read CONTROL OUT data stage.");
return;
}
};
match (req.request_type, req.recipient) {
(RequestType::Standard, Recipient::Device) => match (req.request, req.value) {
(Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => {
self.remote_wakeup_enabled = false;
self.control.accept(stage)
}
(Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => {
self.remote_wakeup_enabled = true;
self.control.accept(stage)
}
(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)
}
},
_ => self.control.reject(),
},
(RequestType::Standard, Recipient::Endpoint) => match (req.request, req.value) {
(Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => {
let ep_addr = ((req.index as u8) & 0x8f).into();
self.bus.set_stalled(ep_addr, true);
self.control.accept(stage)
}
(Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => {
let ep_addr = ((req.index as u8) & 0x8f).into();
self.bus.set_stalled(ep_addr, false);
self.control.accept(stage)
}
_ => self.control.reject(),
},
(_, Recipient::Interface) => {
let handler = self
.interfaces
.iter_mut()
.find(|(i, _)| req.index == *i as _)
.map(|(_, h)| h);
match handler {
Some(handler) => {
let response = match (req.request_type, req.request) {
(RequestType::Standard, Request::SET_INTERFACE) => {
handler.set_interface(req.value)
}
_ => handler.control_out(req, data),
};
match response {
OutResponse::Accepted => self.control.accept(stage),
OutResponse::Rejected => self.control.reject(),
}
}
None => self.control.reject(),
}
}
_ => self.control.reject(),
}
}
async fn handle_control_in(&mut self, req: Request, mut stage: DataInStage) {
// If we don't have an address yet, respond with max 1 packet.
// The host doesn't know our EP0 max packet size yet, and might assume
// a full-length packet is a short packet, thinking we're done sending data.
// See https://github.com/hathach/tinyusb/issues/184
const DEVICE_DESCRIPTOR_LEN: u8 = 18;
if self.pending_address == 0
&& self.config.max_packet_size_0 < DEVICE_DESCRIPTOR_LEN
&& (self.config.max_packet_size_0 as usize) < stage.length
{
trace!("received control req while not addressed: capping response to 1 packet.");
stage.length = self.config.max_packet_size_0 as _;
}
match (req.request_type, req.recipient) {
(RequestType::Standard, Recipient::Device) => match req.request {
Request::GET_STATUS => {
let mut status: u16 = 0x0000;
if self.self_powered {
status |= 0x0001;
}
if self.remote_wakeup_enabled {
status |= 0x0002;
}
self.control.accept_in(&status.to_le_bytes(), stage).await
}
Request::GET_DESCRIPTOR => self.handle_get_descriptor(req, stage).await,
Request::GET_CONFIGURATION => {
let status = match self.device_state {
UsbDeviceState::Configured => CONFIGURATION_VALUE,
_ => CONFIGURATION_NONE,
};
self.control.accept_in(&status.to_le_bytes(), stage).await
}
_ => self.control.reject(),
},
(RequestType::Standard, Recipient::Endpoint) => match req.request {
Request::GET_STATUS => {
let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into();
let mut status: u16 = 0x0000;
if self.bus.is_stalled(ep_addr) {
status |= 0x0001;
}
self.control.accept_in(&status.to_le_bytes(), stage).await
}
_ => self.control.reject(),
},
(_, Recipient::Interface) => {
let handler = self
.interfaces
.iter_mut()
.find(|(i, _)| req.index == *i as _)
.map(|(_, h)| h);
match handler {
Some(handler) => {
let response = match (req.request_type, req.request) {
(RequestType::Standard, Request::GET_STATUS) => {
handler.get_status(self.control_buf)
}
(RequestType::Standard, Request::GET_INTERFACE) => {
handler.get_interface(self.control_buf)
}
_ => handler.control_in(req, self.control_buf),
};
match response {
InResponse::Accepted(data) => self.control.accept_in(data, stage).await,
InResponse::Rejected => self.control.reject(),
}
}
None => self.control.reject(),
}
}
_ => self.control.reject(),
}
}
async fn handle_get_descriptor(&mut self, req: Request, stage: DataInStage) {
let (dtype, index) = req.descriptor_type_index();
match dtype {
descriptor_type::BOS => self.control.accept_in(self.bos_descriptor, stage).await,
descriptor_type::DEVICE => self.control.accept_in(self.device_descriptor, stage).await,
descriptor_type::CONFIGURATION => {
self.control.accept_in(self.config_descriptor, stage).await
}
descriptor_type::STRING => {
if index == 0 {
self.control
.accept_in_writer(req, stage, |w| {
w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes());
})
.await
} else {
let s = match index {
1 => self.config.manufacturer,
2 => self.config.product,
3 => self.config.serial_number,
_ => {
let _index = StringIndex::new(index);
let _lang_id = req.index;
// TODO
None
}
};
if let Some(s) = s {
self.control
.accept_in_writer(req, stage, |w| w.string(s))
.await
} else {
self.control.reject()
}
}
}
_ => self.control.reject(),
}
}
}

138
embassy-usb/src/types.rs Normal file
View File

@ -0,0 +1,138 @@
/// 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
/// for consistency.
///
/// The values of the enum also match the direction bit used in endpoint addresses and control
/// request types.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UsbDirection {
/// Host to device (OUT)
Out = 0x00,
/// Device to host (IN)
In = 0x80,
}
impl From<u8> for UsbDirection {
fn from(value: u8) -> Self {
unsafe { core::mem::transmute(value & 0x80) }
}
}
/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the
/// transfer bmAttributes transfer type bits.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum EndpointType {
/// Control endpoint. Used for device management. Only the host can initiate requests. Usually
/// used only endpoint 0.
Control = 0b00,
/// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet.
Isochronous = 0b01,
/// Bulk endpoint. Used for large amounts of best-effort reliable data.
Bulk = 0b10,
/// Interrupt endpoint. Used for small amounts of time-critical reliable data.
Interrupt = 0b11,
}
/// Type-safe endpoint address.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndpointAddress(u8);
impl From<u8> for EndpointAddress {
#[inline]
fn from(addr: u8) -> EndpointAddress {
EndpointAddress(addr)
}
}
impl From<EndpointAddress> for u8 {
#[inline]
fn from(addr: EndpointAddress) -> u8 {
addr.0
}
}
impl EndpointAddress {
const INBITS: u8 = UsbDirection::In as u8;
/// Constructs a new EndpointAddress with the given index and direction.
#[inline]
pub fn from_parts(index: usize, dir: UsbDirection) -> Self {
EndpointAddress(index as u8 | dir as u8)
}
/// Gets the direction part of the address.
#[inline]
pub fn direction(&self) -> UsbDirection {
if (self.0 & Self::INBITS) != 0 {
UsbDirection::In
} else {
UsbDirection::Out
}
}
/// Returns true if the direction is IN, otherwise false.
#[inline]
pub fn is_in(&self) -> bool {
(self.0 & Self::INBITS) != 0
}
/// Returns true if the direction is OUT, otherwise false.
#[inline]
pub fn is_out(&self) -> bool {
(self.0 & Self::INBITS) == 0
}
/// Gets the index part of the endpoint address.
#[inline]
pub fn index(&self) -> usize {
(self.0 & !Self::INBITS) as usize
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndpointInfo {
pub addr: EndpointAddress,
pub ep_type: EndpointType,
pub max_packet_size: u16,
pub interval: u8,
}
/// A handle for a USB interface that contains its number.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InterfaceNumber(u8);
impl InterfaceNumber {
pub(crate) fn new(index: u8) -> InterfaceNumber {
InterfaceNumber(index)
}
}
impl From<InterfaceNumber> for u8 {
fn from(n: InterfaceNumber) -> u8 {
n.0
}
}
/// 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);
impl StringIndex {
pub(crate) fn new(index: u8) -> StringIndex {
StringIndex(index)
}
}
impl From<StringIndex> for u8 {
fn from(i: StringIndex) -> u8 {
i.0
}
}

45
embassy-usb/src/util.rs Normal file
View File

@ -0,0 +1,45 @@
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
#[derive(Debug, Clone)]
pub enum Either<A, B> {
Left(A),
Right(B),
}
pub fn select<A, B>(a: A, b: B) -> Select<A, B>
where
A: Future,
B: Future,
{
Select { a, b }
}
pub struct Select<A, B> {
a: A,
b: B,
}
impl<A: Unpin, B: Unpin> Unpin for Select<A, B> {}
impl<A, B> Future for Select<A, B>
where
A: Future,
B: Future,
{
type Output = Either<A::Output, B::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let a = unsafe { Pin::new_unchecked(&mut this.a) };
let b = unsafe { Pin::new_unchecked(&mut this.b) };
match a.poll(cx) {
Poll::Ready(x) => Poll::Ready(Either::Left(x)),
Poll::Pending => match b.poll(cx) {
Poll::Ready(x) => Poll::Ready(Either::Right(x)),
Poll::Pending => Poll::Pending,
},
}
}
}

View File

@ -1,16 +1,19 @@
[package]
authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
edition = "2018"
edition = "2021"
name = "embassy-nrf-examples"
version = "0.1.0"
[features]
default = ["nightly"]
nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"]
nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid"]
[dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true }
embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true }
embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true }
defmt = "0.3"
defmt-rtt = "0.3"
@ -21,6 +24,5 @@ panic-probe = { version = "0.3", features = ["print-defmt"] }
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
rand = { version = "0.8.4", default-features = false }
embedded-storage = "0.3.0"
usb-device = "0.2"
usbd-serial = "0.1.1"
usbd-hid = "0.5.2"
serde = { version = "1.0.136", default-features = false }

View File

@ -0,0 +1,148 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::*;
use embassy::executor::Spawner;
use embassy::time::Duration;
use embassy_nrf::gpio::{Input, Pin, Pull};
use embassy_nrf::interrupt;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_usb::control::OutResponse;
use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State};
use futures::future::join;
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
use defmt_rtt as _; // global logger
use 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(()) };
info!("Enabling ext hfosc...");
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
while clock.events_hfclkstarted.read().bits() != 1 {}
info!("Waiting for vbus...");
while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
info!("vbus OK");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Tactile Engineering");
config.product = Some("Testy");
config.serial_number = Some("12345678");
config.max_power = 100;
config.max_packet_size_0 = 64;
// 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 control_buf = [0; 16];
let request_handler = MyRequestHandler {};
let mut state = State::<8, 1>::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);
// Create classes on the builder.
let hid = HidClass::with_output_ep(
&mut builder,
&mut state,
KeyboardReport::desc(),
Some(&request_handler),
60,
64,
);
// Build the builder.
let mut usb = builder.build().await;
// Run the USB device.
let usb_fut = usb.run();
let mut button = Input::new(p.P0_11.degrade(), Pull::Up);
let (mut hid_in, hid_out) = hid.split();
// Do stuff with the class!
let in_fut = async {
loop {
button.wait_for_low().await;
info!("PRESSED");
let report = KeyboardReport {
keycodes: [4, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match hid_in.serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
button.wait_for_high().await;
info!("RELEASED");
let report = KeyboardReport {
keycodes: [0, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match hid_in.serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
};
let out_fut = async {
hid_out.run(false, &request_handler).await;
};
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, join(in_fut, out_fut)).await;
}
struct MyRequestHandler {}
impl RequestHandler for MyRequestHandler {
fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}
fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
info!("Get idle rate for {:?}", id);
None
}
}

View File

@ -0,0 +1,129 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::*;
use embassy::executor::Spawner;
use embassy::time::{Duration, Timer};
use embassy_nrf::interrupt;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_usb::control::OutResponse;
use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State};
use futures::future::join;
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
use defmt_rtt as _; // global logger
use 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(()) };
info!("Enabling ext hfosc...");
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
while clock.events_hfclkstarted.read().bits() != 1 {}
info!("Waiting for vbus...");
while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
info!("vbus OK");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Tactile Engineering");
config.product = Some("Testy");
config.serial_number = Some("12345678");
config.max_power = 100;
// 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 control_buf = [0; 16];
let request_handler = MyRequestHandler {};
let mut control = State::<5, 0>::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);
// Create classes on the builder.
let mut hid = HidClass::new(
&mut builder,
&mut control,
MouseReport::desc(),
Some(&request_handler),
60,
8,
);
// Build the builder.
let mut usb = builder.build().await;
// Run the USB device.
let usb_fut = usb.run();
// Do stuff with the class!
let hid_fut = async {
let mut y: i8 = 5;
loop {
Timer::after(Duration::from_millis(500)).await;
y = -y;
let report = MouseReport {
buttons: 0,
x: 0,
y,
wheel: 0,
pan: 0,
};
match hid.input().serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
}
}
};
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, hid_fut).await;
}
struct MyRequestHandler {}
impl RequestHandler for MyRequestHandler {
fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}
fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
info!("Get idle rate for {:?}", id);
None
}
}

View File

@ -0,0 +1,113 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::{info, panic};
use embassy::executor::Spawner;
use embassy_nrf::interrupt;
use embassy_nrf::pac;
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;
use defmt_rtt as _; // global logger
use 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(()) };
info!("Enabling ext hfosc...");
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
while clock.events_hfclkstarted.read().bits() != 1 {}
info!("Waiting for vbus...");
while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
info!("vbus OK");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let config = Config::new(0xc0de, 0xcafe);
// 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 control_buf = [0; 7];
let mut state = State::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);
// Create classes on the builder.
let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
// Build the builder.
let mut usb = builder.build().await;
// 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<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?;
}
}

View File

@ -0,0 +1,122 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::{info, panic, unwrap};
use embassy::executor::Spawner;
use embassy::util::Forever;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_nrf::{interrupt, peripherals};
use embassy_usb::driver::{ReadError, WriteError};
use embassy_usb::{Config, UsbDevice, UsbDeviceBuilder};
use embassy_usb_serial::{CdcAcmClass, State};
use defmt_rtt as _; // global logger
use panic_probe as _;
type MyDriver = Driver<'static, peripherals::USBD>;
#[embassy::task]
async fn usb_task(mut device: UsbDevice<'static, MyDriver>) {
device.run().await;
}
#[embassy::task]
async fn echo_task(mut class: CdcAcmClass<'static, MyDriver>) {
loop {
class.wait_connection().await;
info!("Connected");
let _ = echo(&mut class).await;
info!("Disconnected");
}
}
#[embassy::main]
async fn main(spawner: Spawner, p: Peripherals) {
let clock: pac::CLOCK = unsafe { mem::transmute(()) };
let power: pac::POWER = unsafe { mem::transmute(()) };
info!("Enabling ext hfosc...");
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
while clock.events_hfclkstarted.read().bits() != 1 {}
info!("Waiting for vbus...");
while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
info!("vbus OK");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let config = Config::new(0xc0de, 0xcafe);
struct Resources {
device_descriptor: [u8; 256],
config_descriptor: [u8; 256],
bos_descriptor: [u8; 256],
control_buf: [u8; 7],
serial_state: State<'static>,
}
static RESOURCES: Forever<Resources> = Forever::new();
let res = RESOURCES.put(Resources {
device_descriptor: [0; 256],
config_descriptor: [0; 256],
bos_descriptor: [0; 256],
control_buf: [0; 7],
serial_state: State::new(),
});
// Create embassy-usb DeviceBuilder using the driver and config.
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut res.device_descriptor,
&mut res.config_descriptor,
&mut res.bos_descriptor,
&mut res.control_buf,
);
// Create classes on the builder.
let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64);
// Build the builder.
let usb = builder.build().await;
unwrap!(spawner.spawn(usb_task(usb)));
unwrap!(spawner.spawn(echo_task(class)));
}
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(class: &mut CdcAcmClass<'static, MyDriver>) -> 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?;
}
}

View File

@ -1,89 +0,0 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::{info, unwrap};
use embassy::executor::Spawner;
use embassy::interrupt::InterruptExt;
use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial};
use embassy_nrf::{interrupt, Peripherals};
use futures::pin_mut;
use usb_device::device::{UsbDeviceBuilder, UsbVidPid};
use defmt_rtt as _; // global logger
use panic_probe as _; // print out panic messages
#[embassy::main]
async fn main(_spawner: Spawner, p: Peripherals) {
let mut rx_buffer = [0u8; 64];
// we send back input + cr + lf
let mut tx_buffer = [0u8; 66];
let usb_bus = UsbBus::new(p.USBD);
let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer);
let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("Fake company")
.product("Serial port")
.serial_number("TEST")
.device_class(0x02)
.build();
let irq = interrupt::take!(USBD);
irq.set_priority(interrupt::Priority::P3);
let mut state = State::new();
let usb = unsafe { Usb::new(&mut state, device, serial, irq) };
pin_mut!(usb);
let (mut reader, mut writer) = usb.as_ref().take_serial_0();
info!("usb initialized!");
unwrap!(
writer
.write_all(b"\r\nInput returned upper cased on CR+LF\r\n")
.await
);
let mut buf = [0u8; 64];
loop {
let mut n = 0;
async {
loop {
let char = unwrap!(reader.read_byte().await);
// throw away, read more on cr, exit on lf
if char == b'\r' {
continue;
} else if char == b'\n' {
break;
}
buf[n] = char;
n += 1;
// stop if we're out of room
if n == buf.len() {
break;
}
}
}
.await;
if n > 0 {
for char in buf[..n].iter_mut() {
// upper case
if 0x61 <= *char && *char <= 0x7a {
*char &= !0x20;
}
}
unwrap!(writer.write_all(&buf[..n]).await);
unwrap!(writer.write_all(b"\r\n").await);
unwrap!(writer.flush().await);
}
}
}

View File

@ -1,66 +0,0 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::{info, unwrap};
use embassy::executor::Spawner;
use embassy::interrupt::InterruptExt;
use embassy::io::{read_line, AsyncWriteExt};
use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial};
use embassy_nrf::{interrupt, Peripherals};
use futures::pin_mut;
use usb_device::device::{UsbDeviceBuilder, UsbVidPid};
use defmt_rtt as _; // global logger
use panic_probe as _; // print out panic messages
#[embassy::main]
async fn main(_spawner: Spawner, p: Peripherals) {
let mut rx_buffer = [0u8; 64];
// we send back input + cr + lf
let mut tx_buffer = [0u8; 66];
let usb_bus = UsbBus::new(p.USBD);
let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer);
let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("Fake company")
.product("Serial port")
.serial_number("TEST")
.device_class(0x02)
.build();
let irq = interrupt::take!(USBD);
irq.set_priority(interrupt::Priority::P3);
let mut state = State::new();
let usb = unsafe { Usb::new(&mut state, device, serial, irq) };
pin_mut!(usb);
let (mut reader, mut writer) = usb.as_ref().take_serial_0();
info!("usb initialized!");
unwrap!(
writer
.write_all(b"\r\nInput returned upper cased on CR+LF\r\n")
.await
);
let mut buf = [0u8; 64];
loop {
let n = unwrap!(read_line(&mut reader, &mut buf).await);
for char in buf[..n].iter_mut() {
// upper case
if 0x61 <= *char && *char <= 0x7a {
*char &= !0x20;
}
}
unwrap!(writer.write_all(&buf[..n]).await);
unwrap!(writer.write_all(b"\r\n").await);
unwrap!(writer.flush().await);
}
}