From f27a47a37b59bf3b9079f4d4d5f43caf7b7872f8 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 26 Sep 2022 13:00:21 +0200 Subject: usb: move classes into the `embassy-usb` crate. --- embassy-usb-hid/Cargo.toml | 24 -- embassy-usb-hid/src/fmt.rs | 225 ------------ embassy-usb-hid/src/lib.rs | 511 --------------------------- embassy-usb-ncm/Cargo.toml | 17 - embassy-usb-ncm/src/fmt.rs | 225 ------------ embassy-usb-ncm/src/lib.rs | 483 ------------------------- embassy-usb-serial/Cargo.toml | 17 - embassy-usb-serial/src/fmt.rs | 225 ------------ embassy-usb-serial/src/lib.rs | 359 ------------------- embassy-usb/Cargo.toml | 9 +- embassy-usb/src/class/cdc_acm.rs | 354 +++++++++++++++++++ embassy-usb/src/class/cdc_ncm.rs | 478 +++++++++++++++++++++++++ embassy-usb/src/class/hid.rs | 504 ++++++++++++++++++++++++++ embassy-usb/src/class/mod.rs | 3 + embassy-usb/src/lib.rs | 1 + examples/nrf/Cargo.toml | 5 +- examples/nrf/src/bin/usb_ethernet.rs | 2 +- examples/nrf/src/bin/usb_hid_keyboard.rs | 4 +- examples/nrf/src/bin/usb_hid_mouse.rs | 4 +- examples/nrf/src/bin/usb_serial.rs | 2 +- examples/nrf/src/bin/usb_serial_multitask.rs | 2 +- examples/rp/Cargo.toml | 2 - examples/rp/src/bin/usb_ethernet.rs | 2 +- examples/rp/src/bin/usb_serial.rs | 2 +- examples/stm32f1/Cargo.toml | 1 - examples/stm32f1/src/bin/usb_serial.rs | 2 +- examples/stm32f3/Cargo.toml | 2 - examples/stm32f3/src/bin/usb_serial.rs | 2 +- examples/stm32l5/Cargo.toml | 3 - examples/stm32l5/src/bin/usb_ethernet.rs | 2 +- examples/stm32l5/src/bin/usb_hid_mouse.rs | 4 +- examples/stm32l5/src/bin/usb_serial.rs | 2 +- 32 files changed, 1364 insertions(+), 2114 deletions(-) delete mode 100644 embassy-usb-hid/Cargo.toml delete mode 100644 embassy-usb-hid/src/fmt.rs delete mode 100644 embassy-usb-hid/src/lib.rs delete mode 100644 embassy-usb-ncm/Cargo.toml delete mode 100644 embassy-usb-ncm/src/fmt.rs delete mode 100644 embassy-usb-ncm/src/lib.rs delete mode 100644 embassy-usb-serial/Cargo.toml delete mode 100644 embassy-usb-serial/src/fmt.rs delete mode 100644 embassy-usb-serial/src/lib.rs create mode 100644 embassy-usb/src/class/cdc_acm.rs create mode 100644 embassy-usb/src/class/cdc_ncm.rs create mode 100644 embassy-usb/src/class/hid.rs create mode 100644 embassy-usb/src/class/mod.rs diff --git a/embassy-usb-hid/Cargo.toml b/embassy-usb-hid/Cargo.toml deleted file mode 100644 index 2f7733dc6..000000000 --- a/embassy-usb-hid/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "embassy-usb-hid" -version = "0.1.0" -edition = "2021" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-hid-v$VERSION/embassy-usb-hid/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-hid/src/" -features = ["defmt"] -target = "thumbv7em-none-eabi" - -[features] -default = ["usbd-hid"] -usbd-hid = ["dep:usbd-hid", "ssmarshal"] - -[dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-usb = { version = "0.1.0", path = "../embassy-usb" } - -defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } -usbd-hid = { version = "0.6.0", optional = true } -ssmarshal = { version = "1.0", default-features = false, optional = true } -futures-util = { version = "0.3.21", default-features = false } diff --git a/embassy-usb-hid/src/fmt.rs b/embassy-usb-hid/src/fmt.rs deleted file mode 100644 index 066970813..000000000 --- a/embassy-usb-hid/src/fmt.rs +++ /dev/null @@ -1,225 +0,0 @@ -#![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; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs deleted file mode 100644 index 8b181aec8..000000000 --- a/embassy-usb-hid/src/lib.rs +++ /dev/null @@ -1,511 +0,0 @@ -#![no_std] -#![feature(type_alias_impl_trait)] - -//! Implements HID functionality for a usb-device device. - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; - -use core::mem::MaybeUninit; -use core::ops::Range; -use core::sync::atomic::{AtomicUsize, Ordering}; - -use embassy_usb::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; -use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use embassy_usb::Builder; -#[cfg(feature = "usbd-hid")] -use ssmarshal::serialize; -#[cfg(feature = "usbd-hid")] -use usbd_hid::descriptor::AsInputReport; - -const USB_CLASS_HID: u8 = 0x03; -const USB_SUBCLASS_NONE: u8 = 0x00; -const USB_PROTOCOL_NONE: u8 = 0x00; - -// HID -const HID_DESC_DESCTYPE_HID: u8 = 0x21; -const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; -const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; -const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; - -const HID_REQ_SET_IDLE: u8 = 0x0a; -const HID_REQ_GET_IDLE: u8 = 0x02; -const HID_REQ_GET_REPORT: u8 = 0x01; -const HID_REQ_SET_REPORT: u8 = 0x09; -const HID_REQ_GET_PROTOCOL: u8 = 0x03; -const HID_REQ_SET_PROTOCOL: u8 = 0x0b; - -pub struct Config<'d> { - /// HID report descriptor. - pub report_descriptor: &'d [u8], - - /// Handler for control requests. - pub request_handler: Option<&'d dyn RequestHandler>, - - /// Configures how frequently the host should poll for reading/writing HID reports. - /// - /// A lower value means better throughput & latency, at the expense - /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for - /// high performance uses, and a value of 255 is good for best-effort usecases. - pub poll_ms: u8, - - /// Max packet size for both the IN and OUT endpoints. - pub max_packet_size: u16, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ReportId { - In(u8), - Out(u8), - Feature(u8), -} - -impl ReportId { - fn try_from(value: u16) -> Result { - match value >> 8 { - 1 => Ok(ReportId::In(value as u8)), - 2 => Ok(ReportId::Out(value as u8)), - 3 => Ok(ReportId::Feature(value as u8)), - _ => Err(()), - } - } -} - -pub struct State<'d> { - control: MaybeUninit>, - out_report_offset: AtomicUsize, -} - -impl<'d> State<'d> { - pub fn new() -> Self { - State { - control: MaybeUninit::uninit(), - out_report_offset: AtomicUsize::new(0), - } - } -} - -pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { - reader: HidReader<'d, D, READ_N>, - writer: HidWriter<'d, D, WRITE_N>, -} - -fn build<'d, D: Driver<'d>>( - builder: &mut Builder<'d, D>, - state: &'d mut State<'d>, - config: Config<'d>, - with_out_endpoint: bool, -) -> (Option, D::EndpointIn, &'d AtomicUsize) { - let control = state.control.write(Control::new( - config.report_descriptor, - config.request_handler, - &state.out_report_offset, - )); - - let len = config.report_descriptor.len(); - - let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); - let mut iface = func.interface(); - iface.handler(control); - let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); - - // HID descriptor - alt.descriptor( - HID_DESC_DESCTYPE_HID, - &[ - // HID Class spec version - HID_DESC_SPEC_1_10[0], - HID_DESC_SPEC_1_10[1], - // Country code not supported - HID_DESC_COUNTRY_UNSPEC, - // Number of following descriptors - 1, - // We have a HID report descriptor the host should read - HID_DESC_DESCTYPE_HID_REPORT, - // HID report descriptor size, - (len & 0xFF) as u8, - (len >> 8 & 0xFF) as u8, - ], - ); - - let ep_in = alt.endpoint_interrupt_in(config.max_packet_size, config.poll_ms); - let ep_out = if with_out_endpoint { - Some(alt.endpoint_interrupt_out(config.max_packet_size, config.poll_ms)) - } else { - None - }; - - (ep_out, ep_in, &state.out_report_offset) -} - -impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWriter<'d, D, READ_N, WRITE_N> { - /// Creates a new HidReaderWriter. - /// - /// This will allocate one IN and one OUT endpoints. If you only need writing (sending) - /// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only. - /// - pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { - let (ep_out, ep_in, offset) = build(builder, state, config, true); - - Self { - reader: HidReader { - ep_out: ep_out.unwrap(), - offset, - }, - writer: HidWriter { ep_in }, - } - } - - /// Splits into seperate readers/writers for input and output reports. - pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { - (self.reader, self.writer) - } - - /// Waits for both IN and OUT endpoints to be enabled. - pub async fn ready(&mut self) -> () { - self.reader.ready().await; - self.writer.ready().await; - } - - /// Writes an input report by serializing the given report structure. - #[cfg(feature = "usbd-hid")] - pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { - self.writer.write_serialize(r).await - } - - /// Writes `report` to its interrupt endpoint. - pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { - self.writer.write(report).await - } - - /// Reads an output report from the Interrupt Out pipe. - /// - /// See [`HidReader::read`]. - pub async fn read(&mut self, buf: &mut [u8]) -> Result { - self.reader.read(buf).await - } -} - -pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { - ep_in: D::EndpointIn, -} - -pub struct HidReader<'d, D: Driver<'d>, const N: usize> { - ep_out: D::EndpointOut, - offset: &'d AtomicUsize, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ReadError { - BufferOverflow, - Disabled, - Sync(Range), -} - -impl From for ReadError { - fn from(val: embassy_usb::driver::EndpointError) -> Self { - use embassy_usb::driver::EndpointError::*; - match val { - BufferOverflow => ReadError::BufferOverflow, - Disabled => ReadError::Disabled, - } - } -} - -impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> { - /// Creates a new HidWriter. - /// - /// This will allocate one IN endpoint only, so the host won't be able to send - /// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead. - /// - /// poll_ms configures how frequently the host should poll for reading/writing - /// HID reports. A lower value means better throughput & latency, at the expense - /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for - /// high performance uses, and a value of 255 is good for best-effort usecases. - pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { - let (ep_out, ep_in, _offset) = build(builder, state, config, false); - - assert!(ep_out.is_none()); - - Self { ep_in } - } - - /// Waits for the interrupt in endpoint to be enabled. - pub async fn ready(&mut self) -> () { - self.ep_in.wait_enabled().await - } - - /// Writes an input report by serializing the given report structure. - #[cfg(feature = "usbd-hid")] - pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { - let mut buf: [u8; N] = [0; N]; - let size = match serialize(&mut buf, r) { - Ok(size) => size, - Err(_) => return Err(EndpointError::BufferOverflow), - }; - self.write(&buf[0..size]).await - } - - /// Writes `report` to its interrupt endpoint. - pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { - assert!(report.len() <= N); - - let max_packet_size = usize::from(self.ep_in.info().max_packet_size); - let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); - for chunk in report.chunks(max_packet_size) { - self.ep_in.write(chunk).await?; - } - - if zlp_needed { - self.ep_in.write(&[]).await?; - } - - Ok(()) - } -} - -impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { - /// Waits for the interrupt out endpoint to be enabled. - pub async fn ready(&mut self) -> () { - self.ep_out.wait_enabled().await - } - - /// Delivers output reports from the Interrupt Out pipe to `handler`. - /// - /// If `use_report_ids` is true, the first byte of the report will be used as - /// the `ReportId` value. Otherwise the `ReportId` value will be 0. - pub async fn run(mut self, use_report_ids: bool, handler: &T) -> ! { - let offset = self.offset.load(Ordering::Acquire); - assert!(offset == 0); - let mut buf = [0; N]; - loop { - match self.read(&mut buf).await { - Ok(len) => { - let id = if use_report_ids { buf[0] } else { 0 }; - handler.set_report(ReportId::Out(id), &buf[..len]); - } - Err(ReadError::BufferOverflow) => warn!( - "Host sent output report larger than the configured maximum output report length ({})", - N - ), - Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, - Err(ReadError::Sync(_)) => unreachable!(), - } - } - } - - /// Reads an output report from the Interrupt Out pipe. - /// - /// **Note:** Any reports sent from the host over the control pipe will be - /// passed to [`RequestHandler::set_report()`] for handling. The application - /// is responsible for ensuring output reports from both pipes are handled - /// correctly. - /// - /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output - /// reports may be split across multiple packets) and this method's future - /// is dropped after some packets have been read, the next call to `read()` - /// will return a [`ReadError::SyncError()`]. The range in the sync error - /// indicates the portion `buf` that was filled by the current call to - /// `read()`. If the dropped future used the same `buf`, then `buf` will - /// contain the full report. - pub async fn read(&mut self, buf: &mut [u8]) -> Result { - assert!(N != 0); - assert!(buf.len() >= N); - - // Read packets from the endpoint - let max_packet_size = usize::from(self.ep_out.info().max_packet_size); - let starting_offset = self.offset.load(Ordering::Acquire); - let mut total = starting_offset; - loop { - for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { - match self.ep_out.read(chunk).await { - Ok(size) => { - total += size; - if size < max_packet_size || total == N { - self.offset.store(0, Ordering::Release); - break; - } else { - self.offset.store(total, Ordering::Release); - } - } - Err(err) => { - self.offset.store(0, Ordering::Release); - return Err(err.into()); - } - } - } - - // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. - if total > 0 { - break; - } - } - - if starting_offset > 0 { - Err(ReadError::Sync(starting_offset..total)) - } else { - Ok(total) - } - } -} - -pub trait RequestHandler { - /// Reads the value of report `id` into `buf` returning the size. - /// - /// Returns `None` if `id` is invalid or no data is available. - fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option { - let _ = (id, buf); - None - } - - /// Sets the value of report `id` to `data`. - fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { - let _ = (id, data); - OutResponse::Rejected - } - - /// Get the idle rate for `id`. - /// - /// If `id` is `None`, get the idle rate for all reports. Returning `None` - /// will reject the control request. Any duration at or above 1.024 seconds - /// or below 4ms will be returned as an indefinite idle rate. - fn get_idle_ms(&self, id: Option) -> Option { - let _ = id; - None - } - - /// Set the idle rate for `id` to `dur`. - /// - /// If `id` is `None`, set the idle rate of all input reports to `dur`. If - /// an indefinite duration is requested, `dur` will be set to `u32::MAX`. - fn set_idle_ms(&self, id: Option, duration_ms: u32) { - let _ = (id, duration_ms); - } -} - -struct Control<'d> { - report_descriptor: &'d [u8], - request_handler: Option<&'d dyn RequestHandler>, - out_report_offset: &'d AtomicUsize, - hid_descriptor: [u8; 9], -} - -impl<'d> Control<'d> { - fn new( - report_descriptor: &'d [u8], - request_handler: Option<&'d dyn RequestHandler>, - out_report_offset: &'d AtomicUsize, - ) -> Self { - Control { - report_descriptor, - request_handler, - out_report_offset, - hid_descriptor: [ - // Length of buf inclusive of size prefix - 9, - // Descriptor type - HID_DESC_DESCTYPE_HID, - // HID Class spec version - HID_DESC_SPEC_1_10[0], - HID_DESC_SPEC_1_10[1], - // Country code not supported - HID_DESC_COUNTRY_UNSPEC, - // Number of following descriptors - 1, - // We have a HID report descriptor the host should read - HID_DESC_DESCTYPE_HID_REPORT, - // HID report descriptor size, - (report_descriptor.len() & 0xFF) as u8, - (report_descriptor.len() >> 8 & 0xFF) as u8, - ], - } - } -} - -impl<'d> ControlHandler for Control<'d> { - fn reset(&mut self) { - self.out_report_offset.store(0, Ordering::Release); - } - - fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { - match (req.value >> 8) as u8 { - HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), - HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), - _ => InResponse::Rejected, - } - } - - fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { - trace!("HID control_out {:?} {=[u8]:x}", req, data); - if let RequestType::Class = req.request_type { - match req.request { - HID_REQ_SET_IDLE => { - if let Some(handler) = self.request_handler { - let id = req.value as u8; - let id = (id != 0).then(|| ReportId::In(id)); - let dur = u32::from(req.value >> 8); - let dur = if dur == 0 { u32::MAX } else { 4 * dur }; - handler.set_idle_ms(id, dur); - } - OutResponse::Accepted - } - HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { - (Ok(id), Some(handler)) => handler.set_report(id, data), - _ => OutResponse::Rejected, - }, - HID_REQ_SET_PROTOCOL => { - if req.value == 1 { - OutResponse::Accepted - } else { - warn!("HID Boot Protocol is unsupported."); - OutResponse::Rejected // UNSUPPORTED: Boot Protocol - } - } - _ => OutResponse::Rejected, - } - } else { - OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - trace!("HID control_in {:?}", req); - match req.request { - HID_REQ_GET_REPORT => { - let size = match ReportId::try_from(req.value) { - Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), - Err(_) => None, - }; - - if let Some(size) = size { - InResponse::Accepted(&buf[0..size]) - } else { - InResponse::Rejected - } - } - HID_REQ_GET_IDLE => { - if let Some(handler) = self.request_handler { - let id = req.value as u8; - let id = (id != 0).then(|| ReportId::In(id)); - if let Some(dur) = handler.get_idle_ms(id) { - let dur = u8::try_from(dur / 4).unwrap_or(0); - buf[0] = dur; - InResponse::Accepted(&buf[0..1]) - } else { - InResponse::Rejected - } - } else { - InResponse::Rejected - } - } - HID_REQ_GET_PROTOCOL => { - // UNSUPPORTED: Boot Protocol - buf[0] = 1; - InResponse::Accepted(&buf[0..1]) - } - _ => InResponse::Rejected, - } - } -} diff --git a/embassy-usb-ncm/Cargo.toml b/embassy-usb-ncm/Cargo.toml deleted file mode 100644 index 15d3db96f..000000000 --- a/embassy-usb-ncm/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "embassy-usb-ncm" -version = "0.1.0" -edition = "2021" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-ncm-v$VERSION/embassy-usb-ncm/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-ncm/src/" -features = ["defmt"] -target = "thumbv7em-none-eabi" - -[dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-usb = { version = "0.1.0", path = "../embassy-usb" } - -defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-ncm/src/fmt.rs b/embassy-usb-ncm/src/fmt.rs deleted file mode 100644 index 066970813..000000000 --- a/embassy-usb-ncm/src/fmt.rs +++ /dev/null @@ -1,225 +0,0 @@ -#![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; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} diff --git a/embassy-usb-ncm/src/lib.rs b/embassy-usb-ncm/src/lib.rs deleted file mode 100644 index e796af28f..000000000 --- a/embassy-usb-ncm/src/lib.rs +++ /dev/null @@ -1,483 +0,0 @@ -#![no_std] - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; - -use core::intrinsics::copy_nonoverlapping; -use core::mem::{size_of, MaybeUninit}; - -use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; -use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use embassy_usb::types::*; -use embassy_usb::Builder; - -/// 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_NCM: u8 = 0x0d; - -const CDC_PROTOCOL_NONE: u8 = 0x00; -const CDC_PROTOCOL_NTB: u8 = 0x01; - -const CS_INTERFACE: u8 = 0x24; -const CDC_TYPE_HEADER: u8 = 0x00; -const CDC_TYPE_UNION: u8 = 0x06; -const CDC_TYPE_ETHERNET: u8 = 0x0F; -const CDC_TYPE_NCM: u8 = 0x1A; - -const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; -//const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; -//const REQ_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40; -//const REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x41; -//const REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x42; -//const REQ_SET_ETHERNET_PACKET_FILTER: u8 = 0x43; -//const REQ_GET_ETHERNET_STATISTIC: u8 = 0x44; -const REQ_GET_NTB_PARAMETERS: u8 = 0x80; -//const REQ_GET_NET_ADDRESS: u8 = 0x81; -//const REQ_SET_NET_ADDRESS: u8 = 0x82; -//const REQ_GET_NTB_FORMAT: u8 = 0x83; -//const REQ_SET_NTB_FORMAT: u8 = 0x84; -//const REQ_GET_NTB_INPUT_SIZE: u8 = 0x85; -const REQ_SET_NTB_INPUT_SIZE: u8 = 0x86; -//const REQ_GET_MAX_DATAGRAM_SIZE: u8 = 0x87; -//const REQ_SET_MAX_DATAGRAM_SIZE: u8 = 0x88; -//const REQ_GET_CRC_MODE: u8 = 0x89; -//const REQ_SET_CRC_MODE: u8 = 0x8A; - -//const NOTIF_MAX_PACKET_SIZE: u16 = 8; -//const NOTIF_POLL_INTERVAL: u8 = 20; - -const NTB_MAX_SIZE: usize = 2048; -const SIG_NTH: u32 = 0x484d434e; -const SIG_NDP_NO_FCS: u32 = 0x304d434e; -const SIG_NDP_WITH_FCS: u32 = 0x314d434e; - -const ALTERNATE_SETTING_DISABLED: u8 = 0x00; -const ALTERNATE_SETTING_ENABLED: u8 = 0x01; - -/// Simple NTB header (NTH+NDP all in one) for sending packets -#[repr(packed)] -#[allow(unused)] -struct NtbOutHeader { - // NTH - nth_sig: u32, - nth_len: u16, - nth_seq: u16, - nth_total_len: u16, - nth_first_index: u16, - - // NDP - ndp_sig: u32, - ndp_len: u16, - ndp_next_index: u16, - ndp_datagram_index: u16, - ndp_datagram_len: u16, - ndp_term1: u16, - ndp_term2: u16, -} - -#[repr(packed)] -#[allow(unused)] -struct NtbParameters { - length: u16, - formats_supported: u16, - in_params: NtbParametersDir, - out_params: NtbParametersDir, -} - -#[repr(packed)] -#[allow(unused)] -struct NtbParametersDir { - max_size: u32, - divisor: u16, - payload_remainder: u16, - out_alignment: u16, - max_datagram_count: u16, -} - -fn byteify(buf: &mut [u8], data: T) -> &[u8] { - let len = size_of::(); - unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) } - &buf[..len] -} - -pub struct State<'a> { - comm_control: MaybeUninit>, - data_control: MaybeUninit, - shared: ControlShared, -} - -impl<'a> State<'a> { - pub fn new() -> Self { - Self { - comm_control: MaybeUninit::uninit(), - data_control: MaybeUninit::uninit(), - shared: Default::default(), - } - } -} - -/// Shared data between Control and CdcAcmClass -struct ControlShared { - mac_addr: [u8; 6], -} - -impl Default for ControlShared { - fn default() -> Self { - ControlShared { mac_addr: [0; 6] } - } -} - -struct CommControl<'a> { - mac_addr_string: StringIndex, - shared: &'a ControlShared, - mac_addr_str: [u8; 12], -} - -impl<'d> ControlHandler for CommControl<'d> { - fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { - match req.request { - REQ_SEND_ENCAPSULATED_COMMAND => { - // We don't actually support encapsulated commands but pretend we do for standards - // compatibility. - OutResponse::Accepted - } - REQ_SET_NTB_INPUT_SIZE => { - // TODO - OutResponse::Accepted - } - _ => OutResponse::Rejected, - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - match req.request { - REQ_GET_NTB_PARAMETERS => { - let res = NtbParameters { - length: size_of::() as _, - formats_supported: 1, // only 16bit, - in_params: NtbParametersDir { - max_size: NTB_MAX_SIZE as _, - divisor: 4, - payload_remainder: 0, - out_alignment: 4, - max_datagram_count: 0, // not used - }, - out_params: NtbParametersDir { - max_size: NTB_MAX_SIZE as _, - divisor: 4, - payload_remainder: 0, - out_alignment: 4, - max_datagram_count: 1, // We only decode 1 packet per NTB - }, - }; - InResponse::Accepted(byteify(buf, res)) - } - _ => InResponse::Rejected, - } - } - - fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { - if index == self.mac_addr_string { - let mac_addr = self.shared.mac_addr; - let s = &mut self.mac_addr_str; - for i in 0..12 { - let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF; - s[i] = match n { - 0x0..=0x9 => b'0' + n, - 0xA..=0xF => b'A' + n - 0xA, - _ => unreachable!(), - } - } - - Some(unsafe { core::str::from_utf8_unchecked(s) }) - } else { - warn!("unknown string index requested"); - None - } - } -} - -struct DataControl {} - -impl ControlHandler for DataControl { - fn set_alternate_setting(&mut self, alternate_setting: u8) { - match alternate_setting { - ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), - ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), - _ => unreachable!(), - } - } -} - -pub struct CdcNcmClass<'d, D: Driver<'d>> { - _comm_if: InterfaceNumber, - comm_ep: D::EndpointIn, - - data_if: InterfaceNumber, - read_ep: D::EndpointOut, - write_ep: D::EndpointIn, - - _control: &'d ControlShared, -} - -impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { - pub fn new( - builder: &mut Builder<'d, D>, - state: &'d mut State<'d>, - mac_address: [u8; 6], - max_packet_size: u16, - ) -> Self { - state.shared.mac_addr = mac_address; - - let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); - - // Control interface - let mut iface = func.interface(); - let mac_addr_string = iface.string(); - iface.handler(state.comm_control.write(CommControl { - mac_addr_string, - shared: &state.shared, - mac_addr_str: [0; 12], - })); - let comm_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); - - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_HEADER, // bDescriptorSubtype - 0x10, - 0x01, // bcdCDC (1.10) - ], - ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_UNION, // bDescriptorSubtype - comm_if.into(), // bControlInterface - u8::from(comm_if) + 1, // bSubordinateInterface - ], - ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_ETHERNET, // bDescriptorSubtype - mac_addr_string.into(), // iMACAddress - 0, // bmEthernetStatistics - 0, // | - 0, // | - 0, // | - 0xea, // wMaxSegmentSize = 1514 - 0x05, // | - 0, // wNumberMCFilters - 0, // | - 0, // bNumberPowerFilters - ], - ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_NCM, // bDescriptorSubtype - 0x00, // bcdNCMVersion - 0x01, // | - 0, // bmNetworkCapabilities - ], - ); - - let comm_ep = alt.endpoint_interrupt_in(8, 255); - - // Data interface - let mut iface = func.interface(); - iface.handler(state.data_control.write(DataControl {})); - let data_if = iface.interface_number(); - let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); - let read_ep = alt.endpoint_bulk_out(max_packet_size); - let write_ep = alt.endpoint_bulk_in(max_packet_size); - - CdcNcmClass { - _comm_if: comm_if, - comm_ep, - data_if, - read_ep, - write_ep, - _control: &state.shared, - } - } - - pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { - ( - Sender { - write_ep: self.write_ep, - seq: 0, - }, - Receiver { - data_if: self.data_if, - comm_ep: self.comm_ep, - read_ep: self.read_ep, - }, - ) - } -} - -pub struct Sender<'d, D: Driver<'d>> { - write_ep: D::EndpointIn, - seq: u16, -} - -impl<'d, D: Driver<'d>> Sender<'d, D> { - pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { - let seq = self.seq; - self.seq = self.seq.wrapping_add(1); - - const MAX_PACKET_SIZE: usize = 64; // TODO unhardcode - const OUT_HEADER_LEN: usize = 28; - - let header = NtbOutHeader { - nth_sig: SIG_NTH, - nth_len: 0x0c, - nth_seq: seq, - nth_total_len: (data.len() + OUT_HEADER_LEN) as u16, - nth_first_index: 0x0c, - - ndp_sig: SIG_NDP_NO_FCS, - ndp_len: 0x10, - ndp_next_index: 0x00, - ndp_datagram_index: OUT_HEADER_LEN as u16, - ndp_datagram_len: data.len() as u16, - ndp_term1: 0x00, - ndp_term2: 0x00, - }; - - // Build first packet on a buffer, send next packets straight from `data`. - let mut buf = [0; MAX_PACKET_SIZE]; - let n = byteify(&mut buf, header); - assert_eq!(n.len(), OUT_HEADER_LEN); - - if OUT_HEADER_LEN + data.len() < MAX_PACKET_SIZE { - // First packet is not full, just send it. - // No need to send ZLP because it's short for sure. - buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data); - self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?; - } else { - let (d1, d2) = data.split_at(MAX_PACKET_SIZE - OUT_HEADER_LEN); - - buf[OUT_HEADER_LEN..].copy_from_slice(d1); - self.write_ep.write(&buf).await?; - - for chunk in d2.chunks(MAX_PACKET_SIZE) { - self.write_ep.write(&chunk).await?; - } - - // Send ZLP if needed. - if d2.len() % MAX_PACKET_SIZE == 0 { - self.write_ep.write(&[]).await?; - } - } - - Ok(()) - } -} - -pub struct Receiver<'d, D: Driver<'d>> { - data_if: InterfaceNumber, - comm_ep: D::EndpointIn, - read_ep: D::EndpointOut, -} - -impl<'d, D: Driver<'d>> Receiver<'d, D> { - /// Reads a single packet from the OUT endpoint. - pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { - // Retry loop - loop { - // read NTB - let mut ntb = [0u8; NTB_MAX_SIZE]; - let mut pos = 0; - loop { - let n = self.read_ep.read(&mut ntb[pos..]).await?; - pos += n; - if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE { - break; - } - } - - let ntb = &ntb[..pos]; - - // Process NTB header (NTH) - let nth = match ntb.get(..12) { - Some(x) => x, - None => { - warn!("Received too short NTB"); - continue; - } - }; - let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap()); - if sig != SIG_NTH { - warn!("Received bad NTH sig."); - continue; - } - let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize; - - // Process NTB Datagram Pointer (NDP) - let ndp = match ntb.get(ndp_idx..ndp_idx + 12) { - Some(x) => x, - None => { - warn!("NTH has an NDP pointer out of range."); - continue; - } - }; - let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap()); - if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS { - warn!("Received bad NDP sig."); - continue; - } - let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize; - let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize; - - if datagram_index == 0 || datagram_len == 0 { - // empty, ignore. This is allowed by the spec, so don't warn. - continue; - } - - // Process actual datagram, finally. - let datagram = match ntb.get(datagram_index..datagram_index + datagram_len) { - Some(x) => x, - None => { - warn!("NDP has a datagram pointer out of range."); - continue; - } - }; - buf[..datagram_len].copy_from_slice(datagram); - - return Ok(datagram_len); - } - } - - /// Waits for the USB host to enable this interface - pub async fn wait_connection(&mut self) -> Result<(), EndpointError> { - loop { - self.read_ep.wait_enabled().await; - self.comm_ep.wait_enabled().await; - - let buf = [ - 0xA1, //bmRequestType - 0x00, //bNotificationType = NETWORK_CONNECTION - 0x01, // wValue = connected - 0x00, - self.data_if.into(), // wIndex = interface - 0x00, - 0x00, // wLength - 0x00, - ]; - match self.comm_ep.write(&buf).await { - Ok(()) => break, // Done! - Err(EndpointError::Disabled) => {} // Got disabled again, wait again. - Err(e) => return Err(e), - } - } - - Ok(()) - } -} diff --git a/embassy-usb-serial/Cargo.toml b/embassy-usb-serial/Cargo.toml deleted file mode 100644 index 9788588e9..000000000 --- a/embassy-usb-serial/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "embassy-usb-serial" -version = "0.1.0" -edition = "2021" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-serial-v$VERSION/embassy-usb-serial/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-serial/src/" -features = ["defmt"] -target = "thumbv7em-none-eabi" - -[dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-usb = { version = "0.1.0", path = "../embassy-usb" } - -defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-serial/src/fmt.rs b/embassy-usb-serial/src/fmt.rs deleted file mode 100644 index 066970813..000000000 --- a/embassy-usb-serial/src/fmt.rs +++ /dev/null @@ -1,225 +0,0 @@ -#![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; -} - -impl Try for Option { - type Ok = T; - type Error = NoneError; - - #[inline] - fn into_result(self) -> Result { - self.ok_or(NoneError) - } -} - -impl Try for Result { - type Ok = T; - type Error = E; - - #[inline] - fn into_result(self) -> Self { - self - } -} diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs deleted file mode 100644 index 15c2bb0a7..000000000 --- a/embassy-usb-serial/src/lib.rs +++ /dev/null @@ -1,359 +0,0 @@ -#![no_std] -#![feature(type_alias_impl_trait)] - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; - -use core::cell::Cell; -use core::mem::{self, MaybeUninit}; -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_sync::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; -use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use embassy_usb::types::*; -use embassy_usb::Builder; - -/// This should be used as `device_class` when building the `UsbDevice`. -pub const USB_CLASS_CDC: u8 = 0x02; - -const USB_CLASS_CDC_DATA: u8 = 0x0a; -const CDC_SUBCLASS_ACM: u8 = 0x02; -const CDC_PROTOCOL_NONE: u8 = 0x00; - -const CS_INTERFACE: u8 = 0x24; -const CDC_TYPE_HEADER: u8 = 0x00; -const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; -const CDC_TYPE_ACM: u8 = 0x02; -const CDC_TYPE_UNION: u8 = 0x06; - -const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; -#[allow(unused)] -const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; -const REQ_SET_LINE_CODING: u8 = 0x20; -const REQ_GET_LINE_CODING: u8 = 0x21; -const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; - -pub struct State<'a> { - control: MaybeUninit>, - shared: ControlShared, -} - -impl<'a> State<'a> { - pub fn new() -> Self { - Self { - control: MaybeUninit::uninit(), - shared: Default::default(), - } - } -} - -/// Packet level implementation of a CDC-ACM serial port. -/// -/// This class can be used directly and it has the least overhead due to directly reading and -/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial -/// port. The following constraints must be followed if you use this class directly: -/// -/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes. -/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes. -/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the -/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) -/// can be sent if there is no other data to send. This is because USB bulk transactions must be -/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. -pub struct CdcAcmClass<'d, D: Driver<'d>> { - _comm_ep: D::EndpointIn, - _data_if: InterfaceNumber, - read_ep: D::EndpointOut, - write_ep: D::EndpointIn, - control: &'d ControlShared, -} - -struct Control<'a> { - shared: &'a ControlShared, -} - -/// Shared data between Control and CdcAcmClass -struct ControlShared { - line_coding: CriticalSectionMutex>, - dtr: AtomicBool, - rts: AtomicBool, -} - -impl Default for ControlShared { - fn default() -> Self { - ControlShared { - dtr: AtomicBool::new(false), - rts: AtomicBool::new(false), - line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - })), - } - } -} - -impl<'a> Control<'a> { - fn shared(&mut self) -> &'a ControlShared { - self.shared - } -} - -impl<'d> ControlHandler for Control<'d> { - fn reset(&mut self) { - let shared = self.shared(); - shared.line_coding.lock(|x| x.set(LineCoding::default())); - shared.dtr.store(false, Ordering::Relaxed); - shared.rts.store(false, Ordering::Relaxed); - } - - fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { - match req.request { - REQ_SEND_ENCAPSULATED_COMMAND => { - // We don't actually support encapsulated commands but pretend we do for standards - // compatibility. - OutResponse::Accepted - } - REQ_SET_LINE_CODING if data.len() >= 7 => { - let coding = LineCoding { - data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), - stop_bits: data[4].into(), - parity_type: data[5].into(), - data_bits: data[6], - }; - self.shared().line_coding.lock(|x| x.set(coding)); - debug!("Set line coding to: {:?}", coding); - - OutResponse::Accepted - } - REQ_SET_CONTROL_LINE_STATE => { - let dtr = (req.value & 0x0001) != 0; - let rts = (req.value & 0x0002) != 0; - - let shared = self.shared(); - shared.dtr.store(dtr, Ordering::Relaxed); - shared.rts.store(rts, Ordering::Relaxed); - debug!("Set dtr {}, rts {}", dtr, rts); - - OutResponse::Accepted - } - _ => OutResponse::Rejected, - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - match req.request { - // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. - REQ_GET_LINE_CODING if req.length == 7 => { - debug!("Sending line coding"); - let coding = self.shared().line_coding.lock(|x| x.get()); - assert!(buf.len() >= 7); - buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - buf[4] = coding.stop_bits as u8; - buf[5] = coding.parity_type as u8; - buf[6] = coding.data_bits; - InResponse::Accepted(&buf[0..7]) - } - _ => InResponse::Rejected, - } - } -} - -impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { - /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For - /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. - pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { - let control = state.control.write(Control { shared: &state.shared }); - - let control_shared = &state.shared; - - assert!(builder.control_buf_len() >= 7); - - let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); - - // Control interface - let mut iface = func.interface(); - iface.handler(control); - let comm_if = iface.interface_number(); - let data_if = u8::from(comm_if) + 1; - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); - - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_HEADER, // bDescriptorSubtype - 0x10, - 0x01, // bcdCDC (1.10) - ], - ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_ACM, // bDescriptorSubtype - 0x00, // bmCapabilities - ], - ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_UNION, // bDescriptorSubtype - comm_if.into(), // bControlInterface - data_if.into(), // bSubordinateInterface - ], - ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype - 0x00, // bmCapabilities - data_if.into(), // bDataInterface - ], - ); - - let comm_ep = alt.endpoint_interrupt_in(8, 255); - - // Data interface - let mut iface = func.interface(); - let data_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); - let read_ep = alt.endpoint_bulk_out(max_packet_size); - let write_ep = alt.endpoint_bulk_in(max_packet_size); - - CdcAcmClass { - _comm_ep: comm_ep, - _data_if: data_if, - read_ep, - write_ep, - control: control_shared, - } - } - - /// Gets the maximum packet size in bytes. - pub fn max_packet_size(&self) -> u16 { - // The size is the same for both endpoints. - self.read_ep.info().max_packet_size - } - - /// Gets the current line coding. The line coding contains information that's mainly relevant - /// for USB to UART serial port emulators, and can be ignored if not relevant. - pub fn line_coding(&self) -> LineCoding { - self.control.line_coding.lock(|x| x.get()) - } - - /// Gets the DTR (data terminal ready) state - pub fn dtr(&self) -> bool { - self.control.dtr.load(Ordering::Relaxed) - } - - /// Gets the RTS (request to send) state - pub fn rts(&self) -> bool { - self.control.rts.load(Ordering::Relaxed) - } - - /// Writes a single packet into the IN endpoint. - pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { - 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 { - self.read_ep.read(data).await - } - - /// Waits for the USB host to enable this interface - pub async fn wait_connection(&mut self) { - self.read_ep.wait_enabled().await - } -} - -/// Number of stop bits for LineCoding -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum StopBits { - /// 1 stop bit - One = 0, - - /// 1.5 stop bits - OnePointFive = 1, - - /// 2 stop bits - Two = 2, -} - -impl From for StopBits { - fn from(value: u8) -> Self { - if value <= 2 { - unsafe { mem::transmute(value) } - } else { - StopBits::One - } - } -} - -/// Parity for LineCoding -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ParityType { - None = 0, - Odd = 1, - Even = 2, - Mark = 3, - Space = 4, -} - -impl From for ParityType { - fn from(value: u8) -> Self { - if value <= 4 { - unsafe { mem::transmute(value) } - } else { - ParityType::None - } - } -} - -/// Line coding parameters -/// -/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can -/// be ignored if you don't plan to interface with a physical UART. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct LineCoding { - stop_bits: StopBits, - data_bits: u8, - parity_type: ParityType, - data_rate: u32, -} - -impl LineCoding { - /// Gets the number of stop bits for UART communication. - pub fn stop_bits(&self) -> StopBits { - self.stop_bits - } - - /// Gets the number of data bits for UART communication. - pub fn data_bits(&self) -> u8 { - self.data_bits - } - - /// Gets the parity type for UART communication. - pub fn parity_type(&self) -> ParityType { - self.parity_type - } - - /// Gets the data rate in bits per second for UART communication. - pub fn data_rate(&self) -> u32 { - self.data_rate - } -} - -impl Default for LineCoding { - fn default() -> Self { - LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - } - } -} diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 660ecc8cc..aad54dbaf 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -11,11 +11,18 @@ target = "thumbv7em-none-eabi" [features] defmt = ["dep:defmt", "embassy-usb-driver/defmt"] +usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] +default = ["usbd-hid"] [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } +embassy-sync = { version = "0.1.0", path = "../embassy-sync" } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -heapless = "0.7.10" \ No newline at end of file +heapless = "0.7.10" + +# for HID +usbd-hid = { version = "0.6.0", optional = true } +ssmarshal = { version = "1.0", default-features = false, optional = true } diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs new file mode 100644 index 000000000..09bb1cc8d --- /dev/null +++ b/embassy-usb/src/class/cdc_acm.rs @@ -0,0 +1,354 @@ +use core::cell::Cell; +use core::mem::{self, MaybeUninit}; +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; + +use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::*; +use crate::Builder; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_CLASS_CDC: u8 = 0x02; + +const USB_CLASS_CDC_DATA: u8 = 0x0a; +const CDC_SUBCLASS_ACM: u8 = 0x02; +const CDC_PROTOCOL_NONE: u8 = 0x00; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; +const CDC_TYPE_ACM: u8 = 0x02; +const CDC_TYPE_UNION: u8 = 0x06; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +#[allow(unused)] +const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +const REQ_SET_LINE_CODING: u8 = 0x20; +const REQ_GET_LINE_CODING: u8 = 0x21; +const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; + +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, +} + +impl<'a> State<'a> { + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + shared: Default::default(), + } + } +} + +/// Packet level implementation of a CDC-ACM serial port. +/// +/// This class can be used directly and it has the least overhead due to directly reading and +/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial +/// port. The following constraints must be followed if you use this class directly: +/// +/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes. +/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes. +/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the +/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) +/// can be sent if there is no other data to send. This is because USB bulk transactions must be +/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. +pub struct CdcAcmClass<'d, D: Driver<'d>> { + _comm_ep: D::EndpointIn, + _data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +struct Control<'a> { + shared: &'a ControlShared, +} + +/// Shared data between Control and CdcAcmClass +struct ControlShared { + line_coding: CriticalSectionMutex>, + dtr: AtomicBool, + rts: AtomicBool, +} + +impl Default for ControlShared { + fn default() -> Self { + ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + } + } +} + +impl<'a> Control<'a> { + fn shared(&mut self) -> &'a ControlShared { + self.shared + } +} + +impl<'d> ControlHandler for Control<'d> { + fn reset(&mut self) { + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(LineCoding::default())); + shared.dtr.store(false, Ordering::Relaxed); + shared.rts.store(false, Ordering::Relaxed); + } + + fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + OutResponse::Accepted + } + REQ_SET_LINE_CODING if data.len() >= 7 => { + let coding = LineCoding { + data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), + stop_bits: data[4].into(), + parity_type: data[5].into(), + data_bits: data[6], + }; + self.shared().line_coding.lock(|x| x.set(coding)); + debug!("Set line coding to: {:?}", coding); + + OutResponse::Accepted + } + REQ_SET_CONTROL_LINE_STATE => { + let dtr = (req.value & 0x0001) != 0; + let rts = (req.value & 0x0002) != 0; + + let shared = self.shared(); + shared.dtr.store(dtr, Ordering::Relaxed); + shared.rts.store(rts, Ordering::Relaxed); + debug!("Set dtr {}, rts {}", dtr, rts); + + OutResponse::Accepted + } + _ => OutResponse::Rejected, + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + debug!("Sending line coding"); + let coding = self.shared().line_coding.lock(|x| x.get()); + assert!(buf.len() >= 7); + buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + buf[4] = coding.stop_bits as u8; + buf[5] = coding.parity_type as u8; + buf[6] = coding.data_bits; + InResponse::Accepted(&buf[0..7]) + } + _ => InResponse::Rejected, + } + } +} + +impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { + /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For + /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { + let control = state.control.write(Control { shared: &state.shared }); + + let control_shared = &state.shared; + + assert!(builder.control_buf_len() >= 7); + + let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); + + // Control interface + let mut iface = func.interface(); + iface.handler(control); + let comm_if = iface.interface_number(); + let data_if = u8::from(comm_if) + 1; + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); + + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_ACM, // bDescriptorSubtype + 0x00, // bmCapabilities + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + data_if.into(), // bSubordinateInterface + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype + 0x00, // bmCapabilities + data_if.into(), // bDataInterface + ], + ); + + let comm_ep = alt.endpoint_interrupt_in(8, 255); + + // Data interface + let mut iface = func.interface(); + let data_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); + let read_ep = alt.endpoint_bulk_out(max_packet_size); + let write_ep = alt.endpoint_bulk_in(max_packet_size); + + CdcAcmClass { + _comm_ep: comm_ep, + _data_if: data_if, + read_ep, + write_ep, + control: control_shared, + } + } + + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(|x| x.get()) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + 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 { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await + } +} + +/// Number of stop bits for LineCoding +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum StopBits { + /// 1 stop bit + One = 0, + + /// 1.5 stop bits + OnePointFive = 1, + + /// 2 stop bits + Two = 2, +} + +impl From for StopBits { + fn from(value: u8) -> Self { + if value <= 2 { + unsafe { mem::transmute(value) } + } else { + StopBits::One + } + } +} + +/// Parity for LineCoding +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ParityType { + None = 0, + Odd = 1, + Even = 2, + Mark = 3, + Space = 4, +} + +impl From for ParityType { + fn from(value: u8) -> Self { + if value <= 4 { + unsafe { mem::transmute(value) } + } else { + ParityType::None + } + } +} + +/// Line coding parameters +/// +/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can +/// be ignored if you don't plan to interface with a physical UART. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LineCoding { + stop_bits: StopBits, + data_bits: u8, + parity_type: ParityType, + data_rate: u32, +} + +impl LineCoding { + /// Gets the number of stop bits for UART communication. + pub fn stop_bits(&self) -> StopBits { + self.stop_bits + } + + /// Gets the number of data bits for UART communication. + pub fn data_bits(&self) -> u8 { + self.data_bits + } + + /// Gets the parity type for UART communication. + pub fn parity_type(&self) -> ParityType { + self.parity_type + } + + /// Gets the data rate in bits per second for UART communication. + pub fn data_rate(&self) -> u32 { + self.data_rate + } +} + +impl Default for LineCoding { + fn default() -> Self { + LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + } + } +} diff --git a/embassy-usb/src/class/cdc_ncm.rs b/embassy-usb/src/class/cdc_ncm.rs new file mode 100644 index 000000000..a39b87e9b --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm.rs @@ -0,0 +1,478 @@ +use core::intrinsics::copy_nonoverlapping; +use core::mem::{size_of, MaybeUninit}; + +use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::*; +use crate::Builder; + +/// 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_NCM: u8 = 0x0d; + +const CDC_PROTOCOL_NONE: u8 = 0x00; +const CDC_PROTOCOL_NTB: u8 = 0x01; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_UNION: u8 = 0x06; +const CDC_TYPE_ETHERNET: u8 = 0x0F; +const CDC_TYPE_NCM: u8 = 0x1A; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +//const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +//const REQ_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40; +//const REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x41; +//const REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x42; +//const REQ_SET_ETHERNET_PACKET_FILTER: u8 = 0x43; +//const REQ_GET_ETHERNET_STATISTIC: u8 = 0x44; +const REQ_GET_NTB_PARAMETERS: u8 = 0x80; +//const REQ_GET_NET_ADDRESS: u8 = 0x81; +//const REQ_SET_NET_ADDRESS: u8 = 0x82; +//const REQ_GET_NTB_FORMAT: u8 = 0x83; +//const REQ_SET_NTB_FORMAT: u8 = 0x84; +//const REQ_GET_NTB_INPUT_SIZE: u8 = 0x85; +const REQ_SET_NTB_INPUT_SIZE: u8 = 0x86; +//const REQ_GET_MAX_DATAGRAM_SIZE: u8 = 0x87; +//const REQ_SET_MAX_DATAGRAM_SIZE: u8 = 0x88; +//const REQ_GET_CRC_MODE: u8 = 0x89; +//const REQ_SET_CRC_MODE: u8 = 0x8A; + +//const NOTIF_MAX_PACKET_SIZE: u16 = 8; +//const NOTIF_POLL_INTERVAL: u8 = 20; + +const NTB_MAX_SIZE: usize = 2048; +const SIG_NTH: u32 = 0x484d434e; +const SIG_NDP_NO_FCS: u32 = 0x304d434e; +const SIG_NDP_WITH_FCS: u32 = 0x314d434e; + +const ALTERNATE_SETTING_DISABLED: u8 = 0x00; +const ALTERNATE_SETTING_ENABLED: u8 = 0x01; + +/// Simple NTB header (NTH+NDP all in one) for sending packets +#[repr(packed)] +#[allow(unused)] +struct NtbOutHeader { + // NTH + nth_sig: u32, + nth_len: u16, + nth_seq: u16, + nth_total_len: u16, + nth_first_index: u16, + + // NDP + ndp_sig: u32, + ndp_len: u16, + ndp_next_index: u16, + ndp_datagram_index: u16, + ndp_datagram_len: u16, + ndp_term1: u16, + ndp_term2: u16, +} + +#[repr(packed)] +#[allow(unused)] +struct NtbParameters { + length: u16, + formats_supported: u16, + in_params: NtbParametersDir, + out_params: NtbParametersDir, +} + +#[repr(packed)] +#[allow(unused)] +struct NtbParametersDir { + max_size: u32, + divisor: u16, + payload_remainder: u16, + out_alignment: u16, + max_datagram_count: u16, +} + +fn byteify(buf: &mut [u8], data: T) -> &[u8] { + let len = size_of::(); + unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) } + &buf[..len] +} + +pub struct State<'a> { + comm_control: MaybeUninit>, + data_control: MaybeUninit, + shared: ControlShared, +} + +impl<'a> State<'a> { + pub fn new() -> Self { + Self { + comm_control: MaybeUninit::uninit(), + data_control: MaybeUninit::uninit(), + shared: Default::default(), + } + } +} + +/// Shared data between Control and CdcAcmClass +struct ControlShared { + mac_addr: [u8; 6], +} + +impl Default for ControlShared { + fn default() -> Self { + ControlShared { mac_addr: [0; 6] } + } +} + +struct CommControl<'a> { + mac_addr_string: StringIndex, + shared: &'a ControlShared, + mac_addr_str: [u8; 12], +} + +impl<'d> ControlHandler for CommControl<'d> { + fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + OutResponse::Accepted + } + REQ_SET_NTB_INPUT_SIZE => { + // TODO + OutResponse::Accepted + } + _ => OutResponse::Rejected, + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + match req.request { + REQ_GET_NTB_PARAMETERS => { + let res = NtbParameters { + length: size_of::() as _, + formats_supported: 1, // only 16bit, + in_params: NtbParametersDir { + max_size: NTB_MAX_SIZE as _, + divisor: 4, + payload_remainder: 0, + out_alignment: 4, + max_datagram_count: 0, // not used + }, + out_params: NtbParametersDir { + max_size: NTB_MAX_SIZE as _, + divisor: 4, + payload_remainder: 0, + out_alignment: 4, + max_datagram_count: 1, // We only decode 1 packet per NTB + }, + }; + InResponse::Accepted(byteify(buf, res)) + } + _ => InResponse::Rejected, + } + } + + fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { + if index == self.mac_addr_string { + let mac_addr = self.shared.mac_addr; + let s = &mut self.mac_addr_str; + for i in 0..12 { + let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF; + s[i] = match n { + 0x0..=0x9 => b'0' + n, + 0xA..=0xF => b'A' + n - 0xA, + _ => unreachable!(), + } + } + + Some(unsafe { core::str::from_utf8_unchecked(s) }) + } else { + warn!("unknown string index requested"); + None + } + } +} + +struct DataControl {} + +impl ControlHandler for DataControl { + fn set_alternate_setting(&mut self, alternate_setting: u8) { + match alternate_setting { + ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), + ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), + _ => unreachable!(), + } + } +} + +pub struct CdcNcmClass<'d, D: Driver<'d>> { + _comm_if: InterfaceNumber, + comm_ep: D::EndpointIn, + + data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + + _control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + mac_address: [u8; 6], + max_packet_size: u16, + ) -> Self { + state.shared.mac_addr = mac_address; + + let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + + // Control interface + let mut iface = func.interface(); + let mac_addr_string = iface.string(); + iface.handler(state.comm_control.write(CommControl { + mac_addr_string, + shared: &state.shared, + mac_addr_str: [0; 12], + })); + let comm_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + u8::from(comm_if) + 1, // bSubordinateInterface + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_ETHERNET, // bDescriptorSubtype + mac_addr_string.into(), // iMACAddress + 0, // bmEthernetStatistics + 0, // | + 0, // | + 0, // | + 0xea, // wMaxSegmentSize = 1514 + 0x05, // | + 0, // wNumberMCFilters + 0, // | + 0, // bNumberPowerFilters + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_NCM, // bDescriptorSubtype + 0x00, // bcdNCMVersion + 0x01, // | + 0, // bmNetworkCapabilities + ], + ); + + let comm_ep = alt.endpoint_interrupt_in(8, 255); + + // Data interface + let mut iface = func.interface(); + iface.handler(state.data_control.write(DataControl {})); + let data_if = iface.interface_number(); + let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); + let read_ep = alt.endpoint_bulk_out(max_packet_size); + let write_ep = alt.endpoint_bulk_in(max_packet_size); + + CdcNcmClass { + _comm_if: comm_if, + comm_ep, + data_if, + read_ep, + write_ep, + _control: &state.shared, + } + } + + pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { + ( + Sender { + write_ep: self.write_ep, + seq: 0, + }, + Receiver { + data_if: self.data_if, + comm_ep: self.comm_ep, + read_ep: self.read_ep, + }, + ) + } +} + +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + seq: u16, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + let seq = self.seq; + self.seq = self.seq.wrapping_add(1); + + const MAX_PACKET_SIZE: usize = 64; // TODO unhardcode + const OUT_HEADER_LEN: usize = 28; + + let header = NtbOutHeader { + nth_sig: SIG_NTH, + nth_len: 0x0c, + nth_seq: seq, + nth_total_len: (data.len() + OUT_HEADER_LEN) as u16, + nth_first_index: 0x0c, + + ndp_sig: SIG_NDP_NO_FCS, + ndp_len: 0x10, + ndp_next_index: 0x00, + ndp_datagram_index: OUT_HEADER_LEN as u16, + ndp_datagram_len: data.len() as u16, + ndp_term1: 0x00, + ndp_term2: 0x00, + }; + + // Build first packet on a buffer, send next packets straight from `data`. + let mut buf = [0; MAX_PACKET_SIZE]; + let n = byteify(&mut buf, header); + assert_eq!(n.len(), OUT_HEADER_LEN); + + if OUT_HEADER_LEN + data.len() < MAX_PACKET_SIZE { + // First packet is not full, just send it. + // No need to send ZLP because it's short for sure. + buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data); + self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?; + } else { + let (d1, d2) = data.split_at(MAX_PACKET_SIZE - OUT_HEADER_LEN); + + buf[OUT_HEADER_LEN..].copy_from_slice(d1); + self.write_ep.write(&buf).await?; + + for chunk in d2.chunks(MAX_PACKET_SIZE) { + self.write_ep.write(&chunk).await?; + } + + // Send ZLP if needed. + if d2.len() % MAX_PACKET_SIZE == 0 { + self.write_ep.write(&[]).await?; + } + } + + Ok(()) + } +} + +pub struct Receiver<'d, D: Driver<'d>> { + data_if: InterfaceNumber, + comm_ep: D::EndpointIn, + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { + // Retry loop + loop { + // read NTB + let mut ntb = [0u8; NTB_MAX_SIZE]; + let mut pos = 0; + loop { + let n = self.read_ep.read(&mut ntb[pos..]).await?; + pos += n; + if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE { + break; + } + } + + let ntb = &ntb[..pos]; + + // Process NTB header (NTH) + let nth = match ntb.get(..12) { + Some(x) => x, + None => { + warn!("Received too short NTB"); + continue; + } + }; + let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap()); + if sig != SIG_NTH { + warn!("Received bad NTH sig."); + continue; + } + let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize; + + // Process NTB Datagram Pointer (NDP) + let ndp = match ntb.get(ndp_idx..ndp_idx + 12) { + Some(x) => x, + None => { + warn!("NTH has an NDP pointer out of range."); + continue; + } + }; + let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap()); + if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS { + warn!("Received bad NDP sig."); + continue; + } + let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize; + let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize; + + if datagram_index == 0 || datagram_len == 0 { + // empty, ignore. This is allowed by the spec, so don't warn. + continue; + } + + // Process actual datagram, finally. + let datagram = match ntb.get(datagram_index..datagram_index + datagram_len) { + Some(x) => x, + None => { + warn!("NDP has a datagram pointer out of range."); + continue; + } + }; + buf[..datagram_len].copy_from_slice(datagram); + + return Ok(datagram_len); + } + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) -> Result<(), EndpointError> { + loop { + self.read_ep.wait_enabled().await; + self.comm_ep.wait_enabled().await; + + let buf = [ + 0xA1, //bmRequestType + 0x00, //bNotificationType = NETWORK_CONNECTION + 0x01, // wValue = connected + 0x00, + self.data_if.into(), // wIndex = interface + 0x00, + 0x00, // wLength + 0x00, + ]; + match self.comm_ep.write(&buf).await { + Ok(()) => break, // Done! + Err(EndpointError::Disabled) => {} // Got disabled again, wait again. + Err(e) => return Err(e), + } + } + + Ok(()) + } +} diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs new file mode 100644 index 000000000..4d1fa995f --- /dev/null +++ b/embassy-usb/src/class/hid.rs @@ -0,0 +1,504 @@ +use core::mem::MaybeUninit; +use core::ops::Range; +use core::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(feature = "usbd-hid")] +use ssmarshal::serialize; +#[cfg(feature = "usbd-hid")] +use usbd_hid::descriptor::AsInputReport; + +use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::Builder; + +const USB_CLASS_HID: u8 = 0x03; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +// HID +const HID_DESC_DESCTYPE_HID: u8 = 0x21; +const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; +const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; +const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; + +const HID_REQ_SET_IDLE: u8 = 0x0a; +const HID_REQ_GET_IDLE: u8 = 0x02; +const HID_REQ_GET_REPORT: u8 = 0x01; +const HID_REQ_SET_REPORT: u8 = 0x09; +const HID_REQ_GET_PROTOCOL: u8 = 0x03; +const HID_REQ_SET_PROTOCOL: u8 = 0x0b; + +pub struct Config<'d> { + /// HID report descriptor. + pub report_descriptor: &'d [u8], + + /// Handler for control requests. + pub request_handler: Option<&'d dyn RequestHandler>, + + /// Configures how frequently the host should poll for reading/writing HID reports. + /// + /// A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub poll_ms: u8, + + /// Max packet size for both the IN and OUT endpoints. + pub max_packet_size: u16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReportId { + In(u8), + Out(u8), + Feature(u8), +} + +impl ReportId { + fn try_from(value: u16) -> Result { + match value >> 8 { + 1 => Ok(ReportId::In(value as u8)), + 2 => Ok(ReportId::Out(value as u8)), + 3 => Ok(ReportId::Feature(value as u8)), + _ => Err(()), + } + } +} + +pub struct State<'d> { + control: MaybeUninit>, + out_report_offset: AtomicUsize, +} + +impl<'d> State<'d> { + pub fn new() -> Self { + State { + control: MaybeUninit::uninit(), + out_report_offset: AtomicUsize::new(0), + } + } +} + +pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { + reader: HidReader<'d, D, READ_N>, + writer: HidWriter<'d, D, WRITE_N>, +} + +fn build<'d, D: Driver<'d>>( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + config: Config<'d>, + with_out_endpoint: bool, +) -> (Option, D::EndpointIn, &'d AtomicUsize) { + let control = state.control.write(Control::new( + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); + + let len = config.report_descriptor.len(); + + let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let mut iface = func.interface(); + iface.handler(control); + let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + + // HID descriptor + alt.descriptor( + HID_DESC_DESCTYPE_HID, + &[ + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (len & 0xFF) as u8, + (len >> 8 & 0xFF) as u8, + ], + ); + + let ep_in = alt.endpoint_interrupt_in(config.max_packet_size, config.poll_ms); + let ep_out = if with_out_endpoint { + Some(alt.endpoint_interrupt_out(config.max_packet_size, config.poll_ms)) + } else { + None + }; + + (ep_out, ep_in, &state.out_report_offset) +} + +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWriter<'d, D, READ_N, WRITE_N> { + /// Creates a new HidReaderWriter. + /// + /// This will allocate one IN and one OUT endpoints. If you only need writing (sending) + /// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only. + /// + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { + let (ep_out, ep_in, offset) = build(builder, state, config, true); + + Self { + reader: HidReader { + ep_out: ep_out.unwrap(), + offset, + }, + writer: HidWriter { ep_in }, + } + } + + /// Splits into seperate readers/writers for input and output reports. + pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { + (self.reader, self.writer) + } + + /// Waits for both IN and OUT endpoints to be enabled. + pub async fn ready(&mut self) -> () { + self.reader.ready().await; + self.writer.ready().await; + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + self.writer.write_serialize(r).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + self.writer.write(report).await + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// See [`HidReader::read`]. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.reader.read(buf).await + } +} + +pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { + ep_in: D::EndpointIn, +} + +pub struct HidReader<'d, D: Driver<'d>, const N: usize> { + ep_out: D::EndpointOut, + offset: &'d AtomicUsize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReadError { + BufferOverflow, + Disabled, + Sync(Range), +} + +impl From for ReadError { + fn from(val: EndpointError) -> Self { + use EndpointError::*; + match val { + BufferOverflow => ReadError::BufferOverflow, + Disabled => ReadError::Disabled, + } + } +} + +impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> { + /// Creates a new HidWriter. + /// + /// This will allocate one IN endpoint only, so the host won't be able to send + /// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { + let (ep_out, ep_in, _offset) = build(builder, state, config, false); + + assert!(ep_out.is_none()); + + Self { ep_in } + } + + /// Waits for the interrupt in endpoint to be enabled. + pub async fn ready(&mut self) -> () { + self.ep_in.wait_enabled().await + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + let mut buf: [u8; N] = [0; N]; + let size = match serialize(&mut buf, r) { + Ok(size) => size, + Err(_) => return Err(EndpointError::BufferOverflow), + }; + self.write(&buf[0..size]).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + assert!(report.len() <= N); + + let max_packet_size = usize::from(self.ep_in.info().max_packet_size); + let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); + for chunk in report.chunks(max_packet_size) { + self.ep_in.write(chunk).await?; + } + + if zlp_needed { + self.ep_in.write(&[]).await?; + } + + Ok(()) + } +} + +impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { + /// Waits for the interrupt out endpoint to be enabled. + pub async fn ready(&mut self) -> () { + self.ep_out.wait_enabled().await + } + + /// Delivers output reports from the Interrupt Out pipe to `handler`. + /// + /// If `use_report_ids` is true, the first byte of the report will be used as + /// the `ReportId` value. Otherwise the `ReportId` value will be 0. + pub async fn run(mut self, use_report_ids: bool, handler: &T) -> ! { + let offset = self.offset.load(Ordering::Acquire); + assert!(offset == 0); + let mut buf = [0; N]; + loop { + match self.read(&mut buf).await { + Ok(len) => { + let id = if use_report_ids { buf[0] } else { 0 }; + handler.set_report(ReportId::Out(id), &buf[..len]); + } + Err(ReadError::BufferOverflow) => warn!( + "Host sent output report larger than the configured maximum output report length ({})", + N + ), + Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, + Err(ReadError::Sync(_)) => unreachable!(), + } + } + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// **Note:** Any reports sent from the host over the control pipe will be + /// passed to [`RequestHandler::set_report()`] for handling. The application + /// is responsible for ensuring output reports from both pipes are handled + /// correctly. + /// + /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output + /// reports may be split across multiple packets) and this method's future + /// is dropped after some packets have been read, the next call to `read()` + /// will return a [`ReadError::SyncError()`]. The range in the sync error + /// indicates the portion `buf` that was filled by the current call to + /// `read()`. If the dropped future used the same `buf`, then `buf` will + /// contain the full report. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(N != 0); + assert!(buf.len() >= N); + + // Read packets from the endpoint + let max_packet_size = usize::from(self.ep_out.info().max_packet_size); + let starting_offset = self.offset.load(Ordering::Acquire); + let mut total = starting_offset; + loop { + for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { + match self.ep_out.read(chunk).await { + Ok(size) => { + total += size; + if size < max_packet_size || total == N { + self.offset.store(0, Ordering::Release); + break; + } else { + self.offset.store(total, Ordering::Release); + } + } + Err(err) => { + self.offset.store(0, Ordering::Release); + return Err(err.into()); + } + } + } + + // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. + if total > 0 { + break; + } + } + + if starting_offset > 0 { + Err(ReadError::Sync(starting_offset..total)) + } else { + Ok(total) + } + } +} + +pub trait RequestHandler { + /// Reads the value of report `id` into `buf` returning the size. + /// + /// Returns `None` if `id` is invalid or no data is available. + fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option { + let _ = (id, buf); + None + } + + /// Sets the value of report `id` to `data`. + fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { + let _ = (id, data); + OutResponse::Rejected + } + + /// Get the idle rate for `id`. + /// + /// If `id` is `None`, get the idle rate for all reports. Returning `None` + /// will reject the control request. Any duration at or above 1.024 seconds + /// or below 4ms will be returned as an indefinite idle rate. + fn get_idle_ms(&self, id: Option) -> Option { + let _ = id; + None + } + + /// Set the idle rate for `id` to `dur`. + /// + /// If `id` is `None`, set the idle rate of all input reports to `dur`. If + /// an indefinite duration is requested, `dur` will be set to `u32::MAX`. + fn set_idle_ms(&self, id: Option, duration_ms: u32) { + let _ = (id, duration_ms); + } +} + +struct Control<'d> { + report_descriptor: &'d [u8], + request_handler: Option<&'d dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + hid_descriptor: [u8; 9], +} + +impl<'d> Control<'d> { + fn new( + report_descriptor: &'d [u8], + request_handler: Option<&'d dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + ) -> Self { + Control { + report_descriptor, + request_handler, + out_report_offset, + hid_descriptor: [ + // Length of buf inclusive of size prefix + 9, + // Descriptor type + HID_DESC_DESCTYPE_HID, + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (report_descriptor.len() & 0xFF) as u8, + (report_descriptor.len() >> 8 & 0xFF) as u8, + ], + } + } +} + +impl<'d> ControlHandler for Control<'d> { + fn reset(&mut self) { + self.out_report_offset.store(0, Ordering::Release); + } + + fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { + match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), + HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), + _ => InResponse::Rejected, + } + } + + fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { + trace!("HID control_out {:?} {=[u8]:x}", req, data); + if let RequestType::Class = req.request_type { + match req.request { + HID_REQ_SET_IDLE => { + if let Some(handler) = self.request_handler { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + let dur = u32::from(req.value >> 8); + let dur = if dur == 0 { u32::MAX } else { 4 * dur }; + handler.set_idle_ms(id, dur); + } + OutResponse::Accepted + } + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { + (Ok(id), Some(handler)) => handler.set_report(id, data), + _ => OutResponse::Rejected, + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + OutResponse::Accepted + } else { + warn!("HID Boot Protocol is unsupported."); + OutResponse::Rejected // UNSUPPORTED: Boot Protocol + } + } + _ => OutResponse::Rejected, + } + } else { + OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + trace!("HID control_in {:?}", req); + match req.request { + HID_REQ_GET_REPORT => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + InResponse::Accepted(&buf[0..size]) + } else { + InResponse::Rejected + } + } + HID_REQ_GET_IDLE => { + if let Some(handler) = self.request_handler { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + if let Some(dur) = handler.get_idle_ms(id) { + let dur = u8::try_from(dur / 4).unwrap_or(0); + buf[0] = dur; + InResponse::Accepted(&buf[0..1]) + } else { + InResponse::Rejected + } + } else { + InResponse::Rejected + } + } + HID_REQ_GET_PROTOCOL => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + InResponse::Accepted(&buf[0..1]) + } + _ => InResponse::Rejected, + } + } +} diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs new file mode 100644 index 000000000..af27577a6 --- /dev/null +++ b/embassy-usb/src/class/mod.rs @@ -0,0 +1,3 @@ +pub mod cdc_acm; +pub mod cdc_ncm; +pub mod hid; diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index e1a99cfae..661b84119 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -7,6 +7,7 @@ pub(crate) mod fmt; pub use embassy_usb_driver as driver; mod builder; +pub mod class; pub mod control; pub mod descriptor; mod descriptor_reader; diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index dbc659cda..a5d340c69 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" [features] default = ["nightly"] -nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid", "embassy-usb-ncm", "embedded-io/async", "embassy-net"] +nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net"] [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } @@ -15,9 +15,6 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } -embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true } -embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"], optional = true } embedded-io = "0.3.0" defmt = "0.3" diff --git a/examples/nrf/src/bin/usb_ethernet.rs b/examples/nrf/src/bin/usb_ethernet.rs index 33ca380ff..de93a2b45 100644 --- a/examples/nrf/src/bin/usb_ethernet.rs +++ b/examples/nrf/src/bin/usb_ethernet.rs @@ -15,8 +15,8 @@ use embassy_nrf::usb::{Driver, PowerUsb}; use embassy_nrf::{interrupt, pac, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::Channel; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; use embassy_usb::{Builder, Config, UsbDevice}; -use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 4eb7d37c9..76e198719 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -14,9 +14,9 @@ use embassy_nrf::usb::{Driver, PowerUsb}; use embassy_nrf::{interrupt, pac}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::signal::Signal; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; use embassy_usb::{Builder, Config, DeviceStateHandler}; -use embassy_usb_hid::{HidReaderWriter, ReportId, RequestHandler, State}; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; @@ -67,7 +67,7 @@ async fn main(_spawner: Spawner) { ); // Create classes on the builder. - let config = embassy_usb_hid::Config { + let config = embassy_usb::class::hid::Config { report_descriptor: KeyboardReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index 65fbda1cf..4916a38d4 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -10,9 +10,9 @@ use embassy_futures::join::join; use embassy_nrf::usb::{Driver, PowerUsb}; use embassy_nrf::{interrupt, pac}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; use embassy_usb::{Builder, Config}; -use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State}; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; @@ -59,7 +59,7 @@ async fn main(_spawner: Spawner) { ); // Create classes on the builder. - let config = embassy_usb_hid::Config { + let config = embassy_usb::class::hid::Config { report_descriptor: MouseReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index a740b4e0a..7c9c4184b 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -9,9 +9,9 @@ use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; use embassy_nrf::{interrupt, pac}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::{Builder, Config}; -use embassy_usb_serial::{CdcAcmClass, State}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs index c646c0bbd..93efc2fe6 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf/src/bin/usb_serial_multitask.rs @@ -8,9 +8,9 @@ use defmt::{info, panic, unwrap}; use embassy_executor::Spawner; use embassy_nrf::usb::{Driver, PowerUsb}; use embassy_nrf::{interrupt, pac, peripherals}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::{Builder, Config, UsbDevice}; -use embassy_usb_serial::{CdcAcmClass, State}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 24c3cdd67..3c8f923e7 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -10,9 +10,7 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } -embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 166ffe175..1057fe7fd 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -13,8 +13,8 @@ use embassy_rp::usb::Driver; use embassy_rp::{interrupt, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::Channel; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; use embassy_usb::{Builder, Config, UsbDevice}; -use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs index bf92a1636..b7d6493b4 100644 --- a/examples/rp/src/bin/usb_serial.rs +++ b/examples/rp/src/bin/usb_serial.rs @@ -7,9 +7,9 @@ use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_rp::interrupt; use embassy_rp::usb::{Driver, Instance}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::{Builder, Config}; -use embassy_usb_serial::{CdcAcmClass, State}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 895e043dd..e6553789a 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -9,7 +9,6 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs index a14e728ba..ad92cdeb2 100644 --- a/examples/stm32f1/src/bin/usb_serial.rs +++ b/examples/stm32f1/src/bin/usb_serial.rs @@ -10,9 +10,9 @@ use embassy_stm32::time::Hertz; use embassy_stm32::usb::{Driver, Instance}; use embassy_stm32::{interrupt, Config}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; -use embassy_usb_serial::{CdcAcmClass, State}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index 27f5c260a..f5b0b880c 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -9,8 +9,6 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } -embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs index b9fd20e2b..f6d27c860 100644 --- a/examples/stm32f3/src/bin/usb_serial.rs +++ b/examples/stm32f3/src/bin/usb_serial.rs @@ -10,9 +10,9 @@ use embassy_stm32::time::mhz; use embassy_stm32::usb::{Driver, Instance}; use embassy_stm32::{interrupt, Config}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; -use embassy_usb_serial::{CdcAcmClass, State}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 05945f6bf..9ebab6476 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -11,9 +11,6 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } -embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] } -embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.6.0" diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index c96a83ead..4f36d3f5a 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -15,8 +15,8 @@ use embassy_stm32::usb::Driver; use embassy_stm32::{interrupt, Config}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::Channel; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State}; use embassy_usb::{Builder, UsbDevice}; -use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; use embedded_io::asynch::Write; use rand_core::RngCore; use static_cell::StaticCell; diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs index fa92ceae3..d38ed7496 100644 --- a/examples/stm32l5/src/bin/usb_hid_mouse.rs +++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -9,9 +9,9 @@ use embassy_stm32::rcc::*; use embassy_stm32::usb::Driver; use embassy_stm32::{interrupt, Config}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; use embassy_usb::Builder; -use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State}; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { ); // Create classes on the builder. - let config = embassy_usb_hid::Config { + let config = embassy_usb::class::hid::Config { report_descriptor: MouseReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs index 7484dc832..7562a4e96 100644 --- a/examples/stm32l5/src/bin/usb_serial.rs +++ b/examples/stm32l5/src/bin/usb_serial.rs @@ -8,9 +8,9 @@ use embassy_futures::join::join; use embassy_stm32::rcc::*; use embassy_stm32::usb::{Driver, Instance}; use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; -use embassy_usb_serial::{CdcAcmClass, State}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] -- cgit