wip: experimental async usb stack
This commit is contained in:
parent
c1b3822964
commit
37598a5b37
@ -64,6 +64,7 @@ _gpio-p1 = []
|
|||||||
embassy = { version = "0.1.0", path = "../embassy" }
|
embassy = { version = "0.1.0", path = "../embassy" }
|
||||||
embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["nrf"]}
|
embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["nrf"]}
|
||||||
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
|
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
|
||||||
|
embassy-usb = {version = "0.1.0", path = "../embassy-usb" }
|
||||||
|
|
||||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
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}
|
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"
|
fixed = "1.10.0"
|
||||||
embedded-storage = "0.3.0"
|
embedded-storage = "0.3.0"
|
||||||
cfg-if = "1.0.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" ] }
|
nrf52805-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
|
||||||
nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
|
nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] }
|
||||||
|
@ -1,43 +1,423 @@
|
|||||||
#![macro_use]
|
#![macro_use]
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use core::sync::atomic::{compiler_fence, Ordering};
|
||||||
|
use core::task::Poll;
|
||||||
|
use embassy::interrupt::InterruptExt;
|
||||||
|
use embassy::time::{with_timeout, Duration};
|
||||||
|
use embassy::util::Unborrow;
|
||||||
|
use embassy::waitqueue::AtomicWaker;
|
||||||
|
use embassy_hal_common::unborrow;
|
||||||
|
use embassy_usb::driver::{self, ReadError, WriteError};
|
||||||
|
use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection};
|
||||||
|
use futures::future::poll_fn;
|
||||||
|
use futures::Future;
|
||||||
|
use pac::NVIC;
|
||||||
|
|
||||||
|
pub use embassy_usb;
|
||||||
|
|
||||||
use crate::interrupt::Interrupt;
|
use crate::interrupt::Interrupt;
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
|
|
||||||
use core::marker::PhantomData;
|
static EP0_WAKER: AtomicWaker = AtomicWaker::new();
|
||||||
use embassy::util::Unborrow;
|
|
||||||
use nrf_usbd::{UsbPeripheral, Usbd};
|
|
||||||
use usb_device::bus::UsbBusAllocator;
|
|
||||||
|
|
||||||
pub use embassy_hal_common::usb::*;
|
pub struct Driver<'d, T: Instance> {
|
||||||
|
|
||||||
pub struct UsbBus<'d, T: Instance> {
|
|
||||||
phantom: PhantomData<&'d mut T>,
|
phantom: PhantomData<&'d mut T>,
|
||||||
|
alloc_in: Allocator,
|
||||||
|
alloc_out: Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'d, T: Instance> UsbPeripheral for UsbBus<'d, T> {
|
impl<'d, T: Instance> Driver<'d, T> {
|
||||||
// todo how to use T::regs
|
pub fn new(
|
||||||
const REGISTERS: *const () = pac::USBD::ptr() as *const ();
|
_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> {
|
Self {
|
||||||
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 {
|
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
|
alloc_in: Allocator::new(),
|
||||||
|
alloc_out: Allocator::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_interrupt(_: *mut ()) {
|
||||||
|
let regs = T::regs();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Bus = Bus<'d, T>;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_phantom: PhantomData,
|
||||||
|
info: 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 {
|
||||||
|
_phantom: PhantomData,
|
||||||
|
info: EndpointInfo {
|
||||||
|
addr: ep_addr,
|
||||||
|
ep_type,
|
||||||
|
max_packet_size,
|
||||||
|
interval,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl embassy_hal_common::usb::USBInterrupt for crate::interrupt::USBD {}
|
fn enable(self) -> Self::Bus {
|
||||||
|
let regs = T::regs();
|
||||||
|
|
||||||
|
errata::pre_enable();
|
||||||
|
|
||||||
|
regs.enable.write(|w| w.enable().enabled());
|
||||||
|
|
||||||
|
// Wait until the peripheral is ready.
|
||||||
|
while !regs.eventcause.read().ready().is_ready() {}
|
||||||
|
regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear.
|
||||||
|
|
||||||
|
errata::post_enable();
|
||||||
|
|
||||||
|
unsafe { NVIC::unmask(pac::Interrupt::USBD) };
|
||||||
|
|
||||||
|
// Enable the USB pullup, allowing enumeration.
|
||||||
|
regs.usbpullup.write(|w| w.connect().enabled());
|
||||||
|
info!("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> {
|
||||||
|
#[inline]
|
||||||
|
fn reset(&mut self) {
|
||||||
|
let regs = T::regs();
|
||||||
|
|
||||||
|
// TODO: Initialize ISO buffers
|
||||||
|
|
||||||
|
// XXX this is not spec compliant; the endpoints should only be enabled after the device
|
||||||
|
// has been put in the Configured state. However, usb-device provides no hook to do that
|
||||||
|
unsafe {
|
||||||
|
regs.epinen.write(|w| w.bits(self.alloc_in.used.into()));
|
||||||
|
regs.epouten.write(|w| w.bits(self.alloc_out.used.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//self.busy_in_endpoints = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {}
|
||||||
|
|
||||||
|
pub struct Endpoint<'d, T: Instance, Dir> {
|
||||||
|
_phantom: PhantomData<(&'d mut T, Dir)>,
|
||||||
|
info: EndpointInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance, Dir> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
|
||||||
|
type ReadFuture<'a>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
= impl Future<Output = Result<usize, ReadError>> + 'a;
|
||||||
|
|
||||||
|
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||||
|
async move {
|
||||||
|
let regs = T::regs();
|
||||||
|
|
||||||
|
if buf.len() == 0 {
|
||||||
|
regs.tasks_ep0status.write(|w| unsafe { w.bits(1) });
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for SETUP packet
|
||||||
|
regs.events_ep0setup.reset();
|
||||||
|
regs.intenset.write(|w| w.ep0setup().set());
|
||||||
|
poll_fn(|cx| {
|
||||||
|
EP0_WAKER.register(cx.waker());
|
||||||
|
if regs.events_ep0setup.read().bits() != 0 {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
info!("got SETUP");
|
||||||
|
|
||||||
|
if buf.len() < 8 {
|
||||||
|
return Err(ReadError::BufferOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
Ok(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
|
||||||
|
type WriteFuture<'a>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
= impl Future<Output = Result<(), WriteError>> + 'a;
|
||||||
|
|
||||||
|
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||||
|
async move {
|
||||||
|
info!("write: {:x}", buf);
|
||||||
|
|
||||||
|
let regs = T::regs();
|
||||||
|
|
||||||
|
let ptr = buf.as_ptr() as u32;
|
||||||
|
let len = buf.len() as u32;
|
||||||
|
regs.epin0.ptr.write(|w| unsafe { w.bits(ptr) });
|
||||||
|
regs.epin0.maxcnt.write(|w| unsafe { w.bits(len) });
|
||||||
|
|
||||||
|
regs.events_ep0datadone.reset();
|
||||||
|
regs.events_endepin[0].reset();
|
||||||
|
|
||||||
|
dma_start();
|
||||||
|
|
||||||
|
regs.tasks_startepin[0].write(|w| unsafe { w.bits(1) });
|
||||||
|
info!("write: waiting for endepin...");
|
||||||
|
while regs.events_endepin[0].read().bits() == 0 {}
|
||||||
|
|
||||||
|
dma_end();
|
||||||
|
|
||||||
|
info!("write: waiting for ep0datadone...");
|
||||||
|
regs.intenset.write(|w| w.ep0datadone().set());
|
||||||
|
let res = with_timeout(
|
||||||
|
Duration::from_millis(10),
|
||||||
|
poll_fn(|cx| {
|
||||||
|
EP0_WAKER.register(cx.waker());
|
||||||
|
if regs.events_ep0datadone.read().bits() != 0 {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
// todo wrong error
|
||||||
|
return Err(driver::WriteError::BufferOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("write done");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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 {
|
pub(crate) mod sealed {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -63,3 +443,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
embassy-usb/Cargo.toml
Normal file
12
embassy-usb/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "embassy-usb"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embassy = { version = "0.1.0", path = "../embassy" }
|
||||||
|
|
||||||
|
defmt = { version = "0.3", optional = true }
|
||||||
|
log = { version = "0.4.14", optional = true }
|
||||||
|
cortex-m = "0.7.3"
|
||||||
|
num-traits = { version = "0.2.14", default-features = false }
|
347
embassy-usb/src/builder.rs
Normal file
347
embassy-usb/src/builder.rs
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
use super::descriptor::{BosWriter, DescriptorWriter};
|
||||||
|
use super::driver::{Driver, EndpointAllocError};
|
||||||
|
use super::types::*;
|
||||||
|
use super::UsbDevice;
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
|
||||||
|
bus: 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`].
|
||||||
|
pub fn new(
|
||||||
|
bus: D,
|
||||||
|
config: Config<'d>,
|
||||||
|
device_descriptor_buf: &'d mut [u8],
|
||||||
|
config_descriptor_buf: &'d mut [u8],
|
||||||
|
bos_descriptor_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).unwrap();
|
||||||
|
config_descriptor.configuration(&config).unwrap();
|
||||||
|
bos_descriptor.bos().unwrap();
|
||||||
|
|
||||||
|
UsbDeviceBuilder {
|
||||||
|
bus,
|
||||||
|
config,
|
||||||
|
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 fn build(mut self) -> UsbDevice<'d, D> {
|
||||||
|
self.config_descriptor.end_configuration();
|
||||||
|
self.bos_descriptor.end_bos();
|
||||||
|
|
||||||
|
UsbDevice::build(
|
||||||
|
self.bus,
|
||||||
|
self.config,
|
||||||
|
self.device_descriptor.into_buf(),
|
||||||
|
self.config_descriptor.into_buf(),
|
||||||
|
self.bos_descriptor.writer.into_buf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.bus
|
||||||
|
.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.bus
|
||||||
|
.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_endpoint_out(&mut self, max_packet_size: u16) -> D::EndpointOut {
|
||||||
|
self.alloc_endpoint_out(None, EndpointType::Control, max_packet_size, 0)
|
||||||
|
.expect("alloc_ep 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")
|
||||||
|
}
|
||||||
|
}
|
134
embassy-usb/src/control.rs
Normal file
134
embassy-usb/src/control.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use core::mem;
|
||||||
|
|
||||||
|
use super::types::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum ParseError {
|
||||||
|
InvalidLength,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
pub(crate) fn parse(buf: &[u8]) -> Result<Request, ParseError> {
|
||||||
|
if buf.len() != 8 {
|
||||||
|
return Err(ParseError::InvalidLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rt = buf[0];
|
||||||
|
let recipient = rt & 0b11111;
|
||||||
|
|
||||||
|
Ok(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)
|
||||||
|
}
|
||||||
|
}
|
387
embassy-usb/src/descriptor.rs
Normal file
387
embassy-usb/src/descriptor.rs
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
use super::builder::Config;
|
||||||
|
use super::{types::*, CONFIGURATION_VALUE, DEFAULT_ALTERNATE_SETTING};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Error {
|
||||||
|
BufferFull,
|
||||||
|
InvalidState,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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]) -> Result<(), Error> {
|
||||||
|
let length = descriptor.len();
|
||||||
|
|
||||||
|
if (self.position + 2 + length) > self.buf.len() || (length + 2) > 255 {
|
||||||
|
return Err(Error::BufferFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn device(&mut self, config: &Config) -> Result<(), Error> {
|
||||||
|
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) -> Result<(), Error> {
|
||||||
|
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
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if !self.write_iads {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write(
|
||||||
|
descriptor_type::IAD,
|
||||||
|
&[
|
||||||
|
first_interface.into(), // bFirstInterface
|
||||||
|
interface_count, // bInterfaceCount
|
||||||
|
function_class,
|
||||||
|
function_sub_class,
|
||||||
|
function_protocol,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
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>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if alternate_setting == DEFAULT_ALTERNATE_SETTING {
|
||||||
|
match self.num_interfaces_mark {
|
||||||
|
Some(mark) => self.buf[mark] += 1,
|
||||||
|
None => return Err(Error::InvalidState),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes an endpoint descriptor.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `endpoint` - Endpoint previously allocated with
|
||||||
|
/// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder).
|
||||||
|
pub fn endpoint(&mut self, endpoint: &EndpointInfo) -> Result<(), Error> {
|
||||||
|
match self.num_endpoints_mark {
|
||||||
|
Some(mark) => self.buf[mark] += 1,
|
||||||
|
None => return Err(Error::InvalidState),
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a string descriptor.
|
||||||
|
pub(crate) fn string(&mut self, string: &str) -> Result<(), Error> {
|
||||||
|
let mut pos = self.position;
|
||||||
|
|
||||||
|
if pos + 2 > self.buf.len() {
|
||||||
|
return Err(Error::BufferFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return Err(Error::BufferFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) -> Result<(), Error> {
|
||||||
|
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])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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]) -> Result<(), Error> {
|
||||||
|
match self.num_caps_mark {
|
||||||
|
Some(mark) => self.writer.buf[mark] += 1,
|
||||||
|
None => return Err(Error::InvalidState),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut start = self.writer.position;
|
||||||
|
let blen = data.len();
|
||||||
|
|
||||||
|
if (start + blen + 3) > self.writer.buf.len() || (blen + 3) > 255 {
|
||||||
|
return Err(Error::BufferFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
160
embassy-usb/src/driver.rs
Normal file
160
embassy-usb/src/driver.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
use super::types::*;
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 Bus: 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>;
|
||||||
|
|
||||||
|
/// 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::Bus;
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
/// 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 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 {
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
// 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 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>;
|
||||||
|
}
|
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
|
||||||
|
}
|
||||||
|
}
|
342
embassy-usb/src/lib.rs
Normal file
342
embassy-usb/src/lib.rs
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![feature(generic_associated_types)]
|
||||||
|
|
||||||
|
// This mod MUST go first, so that the others see its macros.
|
||||||
|
pub(crate) mod fmt;
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
mod control;
|
||||||
|
pub mod descriptor;
|
||||||
|
pub mod driver;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
use self::control::*;
|
||||||
|
use self::descriptor::*;
|
||||||
|
use self::driver::*;
|
||||||
|
use self::types::*;
|
||||||
|
|
||||||
|
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 struct UsbDevice<'d, D: Driver<'d>> {
|
||||||
|
driver: D::Bus,
|
||||||
|
control_in: D::EndpointIn,
|
||||||
|
control_out: D::EndpointOut,
|
||||||
|
|
||||||
|
config: Config<'d>,
|
||||||
|
device_descriptor: &'d [u8],
|
||||||
|
config_descriptor: &'d [u8],
|
||||||
|
bos_descriptor: &'d [u8],
|
||||||
|
|
||||||
|
device_state: UsbDeviceState,
|
||||||
|
remote_wakeup_enabled: bool,
|
||||||
|
self_powered: bool,
|
||||||
|
pending_address: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||||
|
pub(crate) fn build(
|
||||||
|
mut driver: D,
|
||||||
|
config: Config<'d>,
|
||||||
|
device_descriptor: &'d [u8],
|
||||||
|
config_descriptor: &'d [u8],
|
||||||
|
bos_descriptor: &'d [u8],
|
||||||
|
) -> Self {
|
||||||
|
let control_out = driver
|
||||||
|
.alloc_endpoint_out(
|
||||||
|
Some(0x00.into()),
|
||||||
|
EndpointType::Control,
|
||||||
|
config.max_packet_size_0 as u16,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("failed to alloc control endpoint");
|
||||||
|
|
||||||
|
let control_in = driver
|
||||||
|
.alloc_endpoint_in(
|
||||||
|
Some(0x80.into()),
|
||||||
|
EndpointType::Control,
|
||||||
|
config.max_packet_size_0 as u16,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("failed to alloc control endpoint");
|
||||||
|
|
||||||
|
// Enable the USB bus.
|
||||||
|
// This prevent further allocation by consuming the driver.
|
||||||
|
let driver = driver.enable();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
driver,
|
||||||
|
config,
|
||||||
|
control_in,
|
||||||
|
control_out,
|
||||||
|
device_descriptor,
|
||||||
|
config_descriptor,
|
||||||
|
bos_descriptor,
|
||||||
|
device_state: UsbDeviceState::Default,
|
||||||
|
remote_wakeup_enabled: false,
|
||||||
|
self_powered: false,
|
||||||
|
pending_address: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) {
|
||||||
|
loop {
|
||||||
|
let mut buf = [0; 8];
|
||||||
|
let n = self.control_out.read(&mut buf).await.unwrap();
|
||||||
|
assert_eq!(n, 8);
|
||||||
|
let req = Request::parse(&buf).unwrap();
|
||||||
|
info!("setup request: {:x}", req);
|
||||||
|
|
||||||
|
// Now that we have properly parsed the setup packet, ensure the end-point is no longer in
|
||||||
|
// a stalled state.
|
||||||
|
self.control_out.set_stalled(false);
|
||||||
|
|
||||||
|
match req.direction {
|
||||||
|
UsbDirection::In => self.handle_control_in(req).await,
|
||||||
|
UsbDirection::Out => self.handle_control_out(req).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_chunked(&mut self, data: &[u8]) -> Result<(), driver::WriteError> {
|
||||||
|
for c in data.chunks(8) {
|
||||||
|
self.control_in.write(c).await?;
|
||||||
|
}
|
||||||
|
if data.len() % 8 == 0 {
|
||||||
|
self.control_in.write(&[]).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn control_out_accept(&mut self, req: Request) {
|
||||||
|
info!("control out accept");
|
||||||
|
// status phase
|
||||||
|
// todo: cleanup
|
||||||
|
self.control_out.read(&mut []).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn control_in_accept(&mut self, req: Request, data: &[u8]) {
|
||||||
|
info!("control accept {:x}", data);
|
||||||
|
|
||||||
|
let len = data.len().min(req.length as _);
|
||||||
|
if let Err(e) = self.write_chunked(&data[..len]).await {
|
||||||
|
info!("write_chunked failed: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// status phase
|
||||||
|
// todo: cleanup
|
||||||
|
self.control_out.read(&mut []).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn control_in_accept_writer(
|
||||||
|
&mut self,
|
||||||
|
req: Request,
|
||||||
|
f: impl FnOnce(&mut DescriptorWriter),
|
||||||
|
) {
|
||||||
|
let mut buf = [0; 256];
|
||||||
|
let mut w = DescriptorWriter::new(&mut buf);
|
||||||
|
f(&mut w);
|
||||||
|
let pos = w.position();
|
||||||
|
self.control_in_accept(req, &buf[..pos]).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_reject(&mut self, req: Request) {
|
||||||
|
self.control_out.set_stalled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_control_out(&mut self, req: Request) {
|
||||||
|
// TODO actually read the data if there's an OUT data phase.
|
||||||
|
|
||||||
|
const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16;
|
||||||
|
const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16;
|
||||||
|
const DEFAULT_ALTERNATE_SETTING_U16: u16 = DEFAULT_ALTERNATE_SETTING as u16;
|
||||||
|
|
||||||
|
match req.request_type {
|
||||||
|
RequestType::Standard => match (req.recipient, req.request, req.value) {
|
||||||
|
(
|
||||||
|
Recipient::Device,
|
||||||
|
Request::CLEAR_FEATURE,
|
||||||
|
Request::FEATURE_DEVICE_REMOTE_WAKEUP,
|
||||||
|
) => {
|
||||||
|
self.remote_wakeup_enabled = false;
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Endpoint, Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => {
|
||||||
|
//self.bus.set_stalled(((req.index as u8) & 0x8f).into(), false);
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
Recipient::Device,
|
||||||
|
Request::SET_FEATURE,
|
||||||
|
Request::FEATURE_DEVICE_REMOTE_WAKEUP,
|
||||||
|
) => {
|
||||||
|
self.remote_wakeup_enabled = true;
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Endpoint, Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => {
|
||||||
|
//self.bus.set_stalled(((req.index as u8) & 0x8f).into(), true);
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Device, Request::SET_ADDRESS, 1..=127) => {
|
||||||
|
self.pending_address = req.value as u8;
|
||||||
|
|
||||||
|
// on NRF the hardware auto-handles SET_ADDRESS.
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => {
|
||||||
|
self.device_state = UsbDeviceState::Configured;
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => {
|
||||||
|
match self.device_state {
|
||||||
|
UsbDeviceState::Default => {
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.device_state = UsbDeviceState::Addressed;
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Interface, Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => {
|
||||||
|
// TODO: do something when alternate settings are implemented
|
||||||
|
self.control_out_accept(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => self.control_reject(req),
|
||||||
|
},
|
||||||
|
_ => self.control_reject(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_control_in(&mut self, req: Request) {
|
||||||
|
match req.request_type {
|
||||||
|
RequestType::Standard => match (req.recipient, req.request) {
|
||||||
|
(Recipient::Device, Request::GET_STATUS) => {
|
||||||
|
let mut status: u16 = 0x0000;
|
||||||
|
if self.self_powered {
|
||||||
|
status |= 0x0001;
|
||||||
|
}
|
||||||
|
if self.remote_wakeup_enabled {
|
||||||
|
status |= 0x0002;
|
||||||
|
}
|
||||||
|
self.control_in_accept(req, &status.to_le_bytes()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Interface, Request::GET_STATUS) => {
|
||||||
|
let status: u16 = 0x0000;
|
||||||
|
self.control_in_accept(req, &status.to_le_bytes()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Endpoint, Request::GET_STATUS) => {
|
||||||
|
let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into();
|
||||||
|
let mut status: u16 = 0x0000;
|
||||||
|
if self.driver.is_stalled(ep_addr) {
|
||||||
|
status |= 0x0001;
|
||||||
|
}
|
||||||
|
self.control_in_accept(req, &status.to_le_bytes()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Device, Request::GET_DESCRIPTOR) => {
|
||||||
|
self.handle_get_descriptor(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Device, Request::GET_CONFIGURATION) => {
|
||||||
|
let status = match self.device_state {
|
||||||
|
UsbDeviceState::Configured => CONFIGURATION_VALUE,
|
||||||
|
_ => CONFIGURATION_NONE,
|
||||||
|
};
|
||||||
|
self.control_in_accept(req, &status.to_le_bytes()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Recipient::Interface, Request::GET_INTERFACE) => {
|
||||||
|
// TODO: change when alternate settings are implemented
|
||||||
|
let status = DEFAULT_ALTERNATE_SETTING;
|
||||||
|
self.control_in_accept(req, &status.to_le_bytes()).await;
|
||||||
|
}
|
||||||
|
_ => self.control_reject(req),
|
||||||
|
},
|
||||||
|
_ => self.control_reject(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_get_descriptor(&mut self, req: Request) {
|
||||||
|
let (dtype, index) = req.descriptor_type_index();
|
||||||
|
let config = self.config.clone();
|
||||||
|
|
||||||
|
match dtype {
|
||||||
|
descriptor_type::BOS => self.control_in_accept(req, self.bos_descriptor).await,
|
||||||
|
descriptor_type::DEVICE => self.control_in_accept(req, self.device_descriptor).await,
|
||||||
|
descriptor_type::CONFIGURATION => {
|
||||||
|
self.control_in_accept(req, self.config_descriptor).await
|
||||||
|
}
|
||||||
|
descriptor_type::STRING => {
|
||||||
|
if index == 0 {
|
||||||
|
self.control_in_accept_writer(req, |w| {
|
||||||
|
w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes())
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.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;
|
||||||
|
None
|
||||||
|
//classes
|
||||||
|
// .iter()
|
||||||
|
// .filter_map(|cls| cls.get_string(index, lang_id))
|
||||||
|
// .nth(0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(s) = s {
|
||||||
|
self.control_in_accept_writer(req, |w| w.string(s).unwrap())
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
self.control_reject(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.control_reject(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,8 @@ nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
|
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"] }
|
||||||
|
|
||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
defmt-rtt = "0.3"
|
defmt-rtt = "0.3"
|
||||||
@ -22,5 +23,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
|
|||||||
rand = { version = "0.8.4", default-features = false }
|
rand = { version = "0.8.4", default-features = false }
|
||||||
embedded-storage = "0.3.0"
|
embedded-storage = "0.3.0"
|
||||||
|
|
||||||
usb-device = "0.2"
|
|
||||||
usbd-serial = "0.1.1"
|
|
||||||
|
356
examples/nrf/src/bin/usb/cdc_acm.rs
Normal file
356
examples/nrf/src/bin/usb/cdc_acm.rs
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
use core::convert::TryInto;
|
||||||
|
use core::mem;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// 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, and the
|
||||||
|
/// method will return a `WouldBlock` error if there is no packet to be read.
|
||||||
|
/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the
|
||||||
|
/// method will return a `WouldBlock` error if the previous packet has not been sent yet.
|
||||||
|
/// - 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_if: InterfaceNumber,
|
||||||
|
comm_ep: D::EndpointIn,
|
||||||
|
data_if: InterfaceNumber,
|
||||||
|
read_ep: D::EndpointOut,
|
||||||
|
write_ep: D::EndpointIn,
|
||||||
|
line_coding: LineCoding,
|
||||||
|
dtr: bool,
|
||||||
|
rts: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
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>, max_packet_size: u16) -> Self {
|
||||||
|
let comm_if = builder.alloc_interface();
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.config_descriptor
|
||||||
|
.interface(comm_if, USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.config_descriptor
|
||||||
|
.write(
|
||||||
|
CS_INTERFACE,
|
||||||
|
&[
|
||||||
|
CDC_TYPE_HEADER, // bDescriptorSubtype
|
||||||
|
0x10,
|
||||||
|
0x01, // bcdCDC (1.10)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.config_descriptor
|
||||||
|
.write(
|
||||||
|
CS_INTERFACE,
|
||||||
|
&[
|
||||||
|
CDC_TYPE_ACM, // bDescriptorSubtype
|
||||||
|
0x00, // bmCapabilities
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.config_descriptor
|
||||||
|
.write(
|
||||||
|
CS_INTERFACE,
|
||||||
|
&[
|
||||||
|
CDC_TYPE_UNION, // bDescriptorSubtype
|
||||||
|
comm_if.into(), // bControlInterface
|
||||||
|
data_if.into(), // bSubordinateInterface
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.config_descriptor
|
||||||
|
.write(
|
||||||
|
CS_INTERFACE,
|
||||||
|
&[
|
||||||
|
CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
|
||||||
|
0x00, // bmCapabilities
|
||||||
|
data_if.into(), // bDataInterface
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder.config_descriptor.endpoint(comm_ep.info()).unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.config_descriptor
|
||||||
|
.interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder.config_descriptor.endpoint(write_ep.info()).unwrap();
|
||||||
|
builder.config_descriptor.endpoint(read_ep.info()).unwrap();
|
||||||
|
|
||||||
|
CdcAcmClass {
|
||||||
|
comm_if,
|
||||||
|
comm_ep,
|
||||||
|
data_if,
|
||||||
|
read_ep,
|
||||||
|
write_ep,
|
||||||
|
line_coding: LineCoding {
|
||||||
|
stop_bits: StopBits::One,
|
||||||
|
data_bits: 8,
|
||||||
|
parity_type: ParityType::None,
|
||||||
|
data_rate: 8_000,
|
||||||
|
},
|
||||||
|
dtr: false,
|
||||||
|
rts: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.line_coding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the DTR (data terminal ready) state
|
||||||
|
pub fn dtr(&self) -> bool {
|
||||||
|
self.dtr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the RTS (request to send) state
|
||||||
|
pub fn rts(&self) -> bool {
|
||||||
|
self.rts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the address of the IN endpoint.
|
||||||
|
pub(crate) fn write_ep_address(&self) -> EndpointAddress {
|
||||||
|
self.write_ep.info().addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
|
||||||
|
fn get_configuration_descriptors(&self, builder.config_descriptor: &mut Descriptorbuilder.config_descriptor) -> Result<()> {
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.line_coding = LineCoding::default();
|
||||||
|
self.dtr = false;
|
||||||
|
self.rts = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_in(&mut self, xfer: ControlIn<B>) {
|
||||||
|
let req = xfer.request();
|
||||||
|
|
||||||
|
if !(req.request_type == control::RequestType::Class
|
||||||
|
&& req.recipient == control::Recipient::Interface
|
||||||
|
&& req.index == u8::from(self.comm_if) as u16)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match req.request {
|
||||||
|
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
|
||||||
|
REQ_GET_LINE_CODING if req.length == 7 => {
|
||||||
|
xfer.accept(|data| {
|
||||||
|
data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
|
||||||
|
data[4] = self.line_coding.stop_bits as u8;
|
||||||
|
data[5] = self.line_coding.parity_type as u8;
|
||||||
|
data[6] = self.line_coding.data_bits;
|
||||||
|
|
||||||
|
Ok(7)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
xfer.reject().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_out(&mut self, xfer: ControlOut<B>) {
|
||||||
|
let req = xfer.request();
|
||||||
|
|
||||||
|
if !(req.request_type == control::RequestType::Class
|
||||||
|
&& req.recipient == control::Recipient::Interface
|
||||||
|
&& req.index == u8::from(self.comm_if) as u16)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match req.request {
|
||||||
|
REQ_SEND_ENCAPSULATED_COMMAND => {
|
||||||
|
// We don't actually support encapsulated commands but pretend we do for standards
|
||||||
|
// compatibility.
|
||||||
|
xfer.accept().ok();
|
||||||
|
}
|
||||||
|
REQ_SET_LINE_CODING if xfer.data().len() >= 7 => {
|
||||||
|
self.line_coding.data_rate =
|
||||||
|
u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap());
|
||||||
|
self.line_coding.stop_bits = xfer.data()[4].into();
|
||||||
|
self.line_coding.parity_type = xfer.data()[5].into();
|
||||||
|
self.line_coding.data_bits = xfer.data()[6];
|
||||||
|
|
||||||
|
xfer.accept().ok();
|
||||||
|
}
|
||||||
|
REQ_SET_CONTROL_LINE_STATE => {
|
||||||
|
self.dtr = (req.value & 0x0001) != 0;
|
||||||
|
self.rts = (req.value & 0x0002) != 0;
|
||||||
|
|
||||||
|
xfer.accept().ok();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
xfer.reject().ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Number of stop bits for LineCoding
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
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)]
|
||||||
|
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.
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
examples/nrf/src/bin/usb/main.rs
Normal file
53
examples/nrf/src/bin/usb/main.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
#[path = "../../example_common.rs"]
|
||||||
|
mod example_common;
|
||||||
|
|
||||||
|
mod cdc_acm;
|
||||||
|
|
||||||
|
use core::mem;
|
||||||
|
use defmt::*;
|
||||||
|
use embassy::executor::Spawner;
|
||||||
|
use embassy_nrf::interrupt;
|
||||||
|
use embassy_nrf::pac;
|
||||||
|
use embassy_nrf::usb::{self, Driver};
|
||||||
|
use embassy_nrf::Peripherals;
|
||||||
|
use embassy_usb::{Config, UsbDeviceBuilder};
|
||||||
|
|
||||||
|
use crate::cdc_acm::CdcAcmClass;
|
||||||
|
|
||||||
|
#[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");
|
||||||
|
|
||||||
|
let irq = interrupt::take!(USBD);
|
||||||
|
let driver = Driver::new(p.USBD, irq);
|
||||||
|
let config = Config::new(0xc0de, 0xcafe);
|
||||||
|
let mut device_descriptor = [0; 256];
|
||||||
|
let mut config_descriptor = [0; 256];
|
||||||
|
let mut bos_descriptor = [0; 256];
|
||||||
|
|
||||||
|
let mut builder = UsbDeviceBuilder::new(
|
||||||
|
driver,
|
||||||
|
config,
|
||||||
|
&mut device_descriptor,
|
||||||
|
&mut config_descriptor,
|
||||||
|
&mut bos_descriptor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut class = CdcAcmClass::new(&mut builder, 64);
|
||||||
|
|
||||||
|
let mut usb = builder.build();
|
||||||
|
usb.run().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