usb: move classes into the embassy-usb
crate.
This commit is contained in:
354
embassy-usb/src/class/cdc_acm.rs
Normal file
354
embassy-usb/src/class/cdc_acm.rs
Normal file
@ -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<Control<'a>>,
|
||||
shared: ControlShared,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
control: MaybeUninit::uninit(),
|
||||
shared: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet level implementation of a CDC-ACM serial port.
|
||||
///
|
||||
/// This class can be used directly and it has the least overhead due to directly reading and
|
||||
/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
|
||||
/// port. The following constraints must be followed if you use this class directly:
|
||||
///
|
||||
/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes.
|
||||
/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes.
|
||||
/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
|
||||
/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
|
||||
/// can be sent if there is no other data to send. This is because USB bulk transactions must be
|
||||
/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
|
||||
pub struct CdcAcmClass<'d, D: Driver<'d>> {
|
||||
_comm_ep: D::EndpointIn,
|
||||
_data_if: InterfaceNumber,
|
||||
read_ep: D::EndpointOut,
|
||||
write_ep: D::EndpointIn,
|
||||
control: &'d ControlShared,
|
||||
}
|
||||
|
||||
struct Control<'a> {
|
||||
shared: &'a ControlShared,
|
||||
}
|
||||
|
||||
/// Shared data between Control and CdcAcmClass
|
||||
struct ControlShared {
|
||||
line_coding: CriticalSectionMutex<Cell<LineCoding>>,
|
||||
dtr: AtomicBool,
|
||||
rts: AtomicBool,
|
||||
}
|
||||
|
||||
impl Default for ControlShared {
|
||||
fn default() -> Self {
|
||||
ControlShared {
|
||||
dtr: AtomicBool::new(false),
|
||||
rts: AtomicBool::new(false),
|
||||
line_coding: CriticalSectionMutex::new(Cell::new(LineCoding {
|
||||
stop_bits: StopBits::One,
|
||||
data_bits: 8,
|
||||
parity_type: ParityType::None,
|
||||
data_rate: 8_000,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Control<'a> {
|
||||
fn shared(&mut self) -> &'a ControlShared {
|
||||
self.shared
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ControlHandler for Control<'d> {
|
||||
fn reset(&mut self) {
|
||||
let shared = self.shared();
|
||||
shared.line_coding.lock(|x| x.set(LineCoding::default()));
|
||||
shared.dtr.store(false, Ordering::Relaxed);
|
||||
shared.rts.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse {
|
||||
match req.request {
|
||||
REQ_SEND_ENCAPSULATED_COMMAND => {
|
||||
// We don't actually support encapsulated commands but pretend we do for standards
|
||||
// compatibility.
|
||||
OutResponse::Accepted
|
||||
}
|
||||
REQ_SET_LINE_CODING if data.len() >= 7 => {
|
||||
let coding = LineCoding {
|
||||
data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()),
|
||||
stop_bits: data[4].into(),
|
||||
parity_type: data[5].into(),
|
||||
data_bits: data[6],
|
||||
};
|
||||
self.shared().line_coding.lock(|x| x.set(coding));
|
||||
debug!("Set line coding to: {:?}", coding);
|
||||
|
||||
OutResponse::Accepted
|
||||
}
|
||||
REQ_SET_CONTROL_LINE_STATE => {
|
||||
let dtr = (req.value & 0x0001) != 0;
|
||||
let rts = (req.value & 0x0002) != 0;
|
||||
|
||||
let shared = self.shared();
|
||||
shared.dtr.store(dtr, Ordering::Relaxed);
|
||||
shared.rts.store(rts, Ordering::Relaxed);
|
||||
debug!("Set dtr {}, rts {}", dtr, rts);
|
||||
|
||||
OutResponse::Accepted
|
||||
}
|
||||
_ => OutResponse::Rejected,
|
||||
}
|
||||
}
|
||||
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
match req.request {
|
||||
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
|
||||
REQ_GET_LINE_CODING if req.length == 7 => {
|
||||
debug!("Sending line coding");
|
||||
let coding = self.shared().line_coding.lock(|x| x.get());
|
||||
assert!(buf.len() >= 7);
|
||||
buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes());
|
||||
buf[4] = coding.stop_bits as u8;
|
||||
buf[5] = coding.parity_type as u8;
|
||||
buf[6] = coding.data_bits;
|
||||
InResponse::Accepted(&buf[0..7])
|
||||
}
|
||||
_ => InResponse::Rejected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
|
||||
/// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For
|
||||
/// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
|
||||
pub fn new(builder: &mut 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<usize, EndpointError> {
|
||||
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<u8> 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<u8> for ParityType {
|
||||
fn from(value: u8) -> Self {
|
||||
if value <= 4 {
|
||||
unsafe { mem::transmute(value) }
|
||||
} else {
|
||||
ParityType::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Line coding parameters
|
||||
///
|
||||
/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
|
||||
/// be ignored if you don't plan to interface with a physical UART.
|
||||
#[derive(Clone, Copy, 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,
|
||||
}
|
||||
}
|
||||
}
|
478
embassy-usb/src/class/cdc_ncm.rs
Normal file
478
embassy-usb/src/class/cdc_ncm.rs
Normal file
@ -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<T>(buf: &mut [u8], data: T) -> &[u8] {
|
||||
let len = size_of::<T>();
|
||||
unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) }
|
||||
&buf[..len]
|
||||
}
|
||||
|
||||
pub struct State<'a> {
|
||||
comm_control: MaybeUninit<CommControl<'a>>,
|
||||
data_control: MaybeUninit<DataControl>,
|
||||
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::<NtbParameters>() 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<usize, EndpointError> {
|
||||
// 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(())
|
||||
}
|
||||
}
|
504
embassy-usb/src/class/hid.rs
Normal file
504
embassy-usb/src/class/hid.rs
Normal file
@ -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<Self, ()> {
|
||||
match value >> 8 {
|
||||
1 => Ok(ReportId::In(value as u8)),
|
||||
2 => Ok(ReportId::Out(value as u8)),
|
||||
3 => Ok(ReportId::Feature(value as u8)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State<'d> {
|
||||
control: MaybeUninit<Control<'d>>,
|
||||
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::EndpointOut>, 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<IR: AsInputReport>(&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<usize, ReadError> {
|
||||
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<usize>),
|
||||
}
|
||||
|
||||
impl From<EndpointError> 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<IR: AsInputReport>(&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<T: RequestHandler>(mut self, use_report_ids: bool, handler: &T) -> ! {
|
||||
let offset = self.offset.load(Ordering::Acquire);
|
||||
assert!(offset == 0);
|
||||
let mut buf = [0; N];
|
||||
loop {
|
||||
match self.read(&mut buf).await {
|
||||
Ok(len) => {
|
||||
let id = if use_report_ids { buf[0] } else { 0 };
|
||||
handler.set_report(ReportId::Out(id), &buf[..len]);
|
||||
}
|
||||
Err(ReadError::BufferOverflow) => warn!(
|
||||
"Host sent output report larger than the configured maximum output report length ({})",
|
||||
N
|
||||
),
|
||||
Err(ReadError::Disabled) => self.ep_out.wait_enabled().await,
|
||||
Err(ReadError::Sync(_)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads an output report from the Interrupt Out pipe.
|
||||
///
|
||||
/// **Note:** Any reports sent from the host over the control pipe will be
|
||||
/// passed to [`RequestHandler::set_report()`] for handling. The application
|
||||
/// is responsible for ensuring output reports from both pipes are handled
|
||||
/// correctly.
|
||||
///
|
||||
/// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output
|
||||
/// reports may be split across multiple packets) and this method's future
|
||||
/// is dropped after some packets have been read, the next call to `read()`
|
||||
/// will return a [`ReadError::SyncError()`]. The range in the sync error
|
||||
/// indicates the portion `buf` that was filled by the current call to
|
||||
/// `read()`. If the dropped future used the same `buf`, then `buf` will
|
||||
/// contain the full report.
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
|
||||
assert!(N != 0);
|
||||
assert!(buf.len() >= N);
|
||||
|
||||
// Read packets from the endpoint
|
||||
let max_packet_size = usize::from(self.ep_out.info().max_packet_size);
|
||||
let starting_offset = self.offset.load(Ordering::Acquire);
|
||||
let mut total = starting_offset;
|
||||
loop {
|
||||
for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) {
|
||||
match self.ep_out.read(chunk).await {
|
||||
Ok(size) => {
|
||||
total += size;
|
||||
if size < max_packet_size || total == N {
|
||||
self.offset.store(0, Ordering::Release);
|
||||
break;
|
||||
} else {
|
||||
self.offset.store(total, Ordering::Release);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.offset.store(0, Ordering::Release);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0.
|
||||
if total > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if starting_offset > 0 {
|
||||
Err(ReadError::Sync(starting_offset..total))
|
||||
} else {
|
||||
Ok(total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RequestHandler {
|
||||
/// Reads the value of report `id` into `buf` returning the size.
|
||||
///
|
||||
/// Returns `None` if `id` is invalid or no data is available.
|
||||
fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> {
|
||||
let _ = (id, buf);
|
||||
None
|
||||
}
|
||||
|
||||
/// Sets the value of report `id` to `data`.
|
||||
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
|
||||
let _ = (id, data);
|
||||
OutResponse::Rejected
|
||||
}
|
||||
|
||||
/// Get the idle rate for `id`.
|
||||
///
|
||||
/// If `id` is `None`, get the idle rate for all reports. Returning `None`
|
||||
/// will reject the control request. Any duration at or above 1.024 seconds
|
||||
/// or below 4ms will be returned as an indefinite idle rate.
|
||||
fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
|
||||
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<ReportId>, 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,
|
||||
}
|
||||
}
|
||||
}
|
3
embassy-usb/src/class/mod.rs
Normal file
3
embassy-usb/src/class/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod cdc_acm;
|
||||
pub mod cdc_ncm;
|
||||
pub mod hid;
|
@ -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;
|
||||
|
Reference in New Issue
Block a user