mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-22 14:53:03 +00:00
Merge #657
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:
commit
37da84129d
4
.github/workflows/rust.yml
vendored
4
.github/workflows/rust.yml
vendored
@ -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}*
|
||||
|
@ -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" ] }
|
||||
|
@ -125,6 +125,7 @@ embassy_hal_common::peripherals! {
|
||||
TEMP,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl_usb!(USBD, USBD, USBD);
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -157,6 +157,7 @@ embassy_hal_common::peripherals! {
|
||||
TEMP,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl_usb!(USBD, USBD, USBD);
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -160,6 +160,7 @@ embassy_hal_common::peripherals! {
|
||||
TEMP,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl_usb!(USBD, USBD, USBD);
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -348,6 +348,7 @@ embassy_hal_common::peripherals! {
|
||||
P1_15,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl_usb!(USBD, USBD, USBD);
|
||||
|
||||
impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0);
|
||||
|
@ -91,6 +91,7 @@ pub mod uarte;
|
||||
feature = "nrf52833",
|
||||
feature = "nrf52840"
|
||||
))]
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod usb;
|
||||
#[cfg(not(feature = "_nrf5340"))]
|
||||
pub mod wdt;
|
||||
|
@ -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 = [
|
||||
®s.epout0,
|
||||
®s.epout1,
|
||||
®s.epout2,
|
||||
®s.epout3,
|
||||
®s.epout4,
|
||||
®s.epout5,
|
||||
®s.epout6,
|
||||
®s.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 = [
|
||||
®s.epin0,
|
||||
®s.epin1,
|
||||
®s.epin2,
|
||||
®s.epin3,
|
||||
®s.epin4,
|
||||
®s.epin5,
|
||||
®s.epin6,
|
||||
®s.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
embassy-usb-hid/Cargo.toml
Normal file
18
embassy-usb-hid/Cargo.toml
Normal 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
225
embassy-usb-hid/src/fmt.rs
Normal 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
522
embassy-usb-hid/src/lib.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
19
embassy-usb-serial/Cargo.toml
Normal file
19
embassy-usb-serial/Cargo.toml
Normal 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 }
|
225
embassy-usb-serial/src/fmt.rs
Normal file
225
embassy-usb-serial/src/fmt.rs
Normal 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
|
||||
}
|
||||
}
|
369
embassy-usb-serial/src/lib.rs
Normal file
369
embassy-usb-serial/src/lib.rs
Normal 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
19
embassy-usb/Cargo.toml
Normal 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
386
embassy-usb/src/builder.rs
Normal 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
330
embassy-usb/src/control.rs
Normal 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();
|
||||
}
|
||||
}
|
369
embassy-usb/src/descriptor.rs
Normal file
369
embassy-usb/src/descriptor.rs
Normal 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
239
embassy-usb/src/driver.rs
Normal 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
225
embassy-usb/src/fmt.rs
Normal 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
344
embassy-usb/src/lib.rs
Normal 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
138
embassy-usb/src/types.rs
Normal 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
45
embassy-usb/src/util.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }
|
||||
|
148
examples/nrf/src/bin/usb_hid_keyboard.rs
Normal file
148
examples/nrf/src/bin/usb_hid_keyboard.rs
Normal 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
|
||||
}
|
||||
}
|
129
examples/nrf/src/bin/usb_hid_mouse.rs
Normal file
129
examples/nrf/src/bin/usb_hid_mouse.rs
Normal 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
|
||||
}
|
||||
}
|
113
examples/nrf/src/bin/usb_serial.rs
Normal file
113
examples/nrf/src/bin/usb_serial.rs
Normal 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?;
|
||||
}
|
||||
}
|
122
examples/nrf/src/bin/usb_serial_multitask.rs
Normal file
122
examples/nrf/src/bin/usb_serial_multitask.rs
Normal 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?;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user