2022-03-28 16:49:17 +02:00
|
|
|
use core::cell::Cell;
|
2022-03-28 02:20:01 +02:00
|
|
|
use core::mem::{self, MaybeUninit};
|
|
|
|
use core::sync::atomic::{AtomicBool, Ordering};
|
2022-06-12 22:15:44 +02:00
|
|
|
|
2022-08-22 21:46:09 +02:00
|
|
|
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
2022-09-26 13:00:21 +02:00
|
|
|
|
|
|
|
use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
|
|
|
|
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
|
|
|
|
use crate::types::*;
|
|
|
|
use crate::Builder;
|
2022-03-09 01:34:35 +01:00
|
|
|
|
|
|
|
/// 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_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;
|
|
|
|
|
2022-03-28 16:49:17 +02:00
|
|
|
pub struct State<'a> {
|
|
|
|
control: MaybeUninit<Control<'a>>,
|
|
|
|
shared: ControlShared,
|
2022-03-28 02:20:01 +02:00
|
|
|
}
|
|
|
|
|
2022-03-28 16:49:17 +02:00
|
|
|
impl<'a> State<'a> {
|
2022-03-28 02:20:01 +02:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
control: MaybeUninit::uninit(),
|
2022-03-28 16:49:17 +02:00
|
|
|
shared: Default::default(),
|
2022-03-28 02:20:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-09 01:34:35 +01:00
|
|
|
/// 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:
|
|
|
|
///
|
2022-03-30 02:16:34 +02:00
|
|
|
/// - `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.
|
2022-03-09 01:34:35 +01:00
|
|
|
/// - 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>> {
|
2022-03-30 02:16:34 +02:00
|
|
|
_comm_ep: D::EndpointIn,
|
|
|
|
_data_if: InterfaceNumber,
|
|
|
|
read_ep: D::EndpointOut,
|
|
|
|
write_ep: D::EndpointIn,
|
2022-03-28 02:20:01 +02:00
|
|
|
control: &'d ControlShared,
|
|
|
|
}
|
|
|
|
|
2022-03-28 16:49:17 +02:00
|
|
|
struct Control<'a> {
|
|
|
|
shared: &'a ControlShared,
|
2022-03-25 21:46:14 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 02:20:01 +02:00
|
|
|
/// Shared data between Control and CdcAcmClass
|
|
|
|
struct ControlShared {
|
|
|
|
line_coding: CriticalSectionMutex<Cell<LineCoding>>,
|
|
|
|
dtr: AtomicBool,
|
|
|
|
rts: AtomicBool,
|
2022-03-09 01:34:35 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 16:49:17 +02:00
|
|
|
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,
|
|
|
|
})),
|
|
|
|
}
|
2022-03-28 02:20:01 +02:00
|
|
|
}
|
|
|
|
}
|
2022-03-28 16:49:17 +02:00
|
|
|
|
|
|
|
impl<'a> Control<'a> {
|
|
|
|
fn shared(&mut self) -> &'a ControlShared {
|
|
|
|
self.shared
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 21:09:24 +02:00
|
|
|
impl<'d> ControlHandler for Control<'d> {
|
2022-03-25 21:46:14 +01:00
|
|
|
fn reset(&mut self) {
|
2022-03-28 02:20:01 +02:00
|
|
|
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);
|
2022-03-25 21:46:14 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 03:30:08 +02:00
|
|
|
fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse {
|
2022-03-27 23:12:57 +02:00
|
|
|
match req.request {
|
|
|
|
REQ_SEND_ENCAPSULATED_COMMAND => {
|
|
|
|
// We don't actually support encapsulated commands but pretend we do for standards
|
|
|
|
// compatibility.
|
2022-03-28 03:30:08 +02:00
|
|
|
OutResponse::Accepted
|
2022-03-27 23:12:57 +02:00
|
|
|
}
|
|
|
|
REQ_SET_LINE_CODING if data.len() >= 7 => {
|
2022-03-28 02:20:01 +02:00
|
|
|
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));
|
2022-03-30 01:30:58 +02:00
|
|
|
debug!("Set line coding to: {:?}", coding);
|
2022-03-27 23:12:57 +02:00
|
|
|
|
2022-03-28 03:30:08 +02:00
|
|
|
OutResponse::Accepted
|
2022-03-27 23:12:57 +02:00
|
|
|
}
|
|
|
|
REQ_SET_CONTROL_LINE_STATE => {
|
2022-03-28 02:20:01 +02:00
|
|
|
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);
|
2022-03-30 01:30:58 +02:00
|
|
|
debug!("Set dtr {}, rts {}", dtr, rts);
|
2022-03-25 21:46:14 +01:00
|
|
|
|
2022-03-28 03:30:08 +02:00
|
|
|
OutResponse::Accepted
|
2022-03-25 21:46:14 +01:00
|
|
|
}
|
2022-03-28 03:30:08 +02:00
|
|
|
_ => OutResponse::Rejected,
|
2022-03-25 21:46:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 21:09:24 +02:00
|
|
|
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
2022-03-27 23:12:57 +02:00
|
|
|
match req.request {
|
|
|
|
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
|
|
|
|
REQ_GET_LINE_CODING if req.length == 7 => {
|
2022-03-30 01:30:58 +02:00
|
|
|
debug!("Sending line coding");
|
2022-03-28 02:20:01 +02:00
|
|
|
let coding = self.shared().line_coding.lock(|x| x.get());
|
2022-03-29 21:09:24 +02:00
|
|
|
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])
|
2022-03-25 21:46:14 +01:00
|
|
|
}
|
2022-03-28 03:34:24 +02:00
|
|
|
_ => InResponse::Rejected,
|
2022-03-25 21:46:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-09 01:34:35 +01:00
|
|
|
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.
|
2022-06-12 22:15:44 +02:00
|
|
|
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 });
|
2022-03-28 03:19:07 +02:00
|
|
|
|
2022-03-28 16:49:17 +02:00
|
|
|
let control_shared = &state.shared;
|
2022-03-28 03:19:07 +02:00
|
|
|
|
2022-03-29 21:09:24 +02:00
|
|
|
assert!(builder.control_buf_len() >= 7);
|
|
|
|
|
2022-04-15 23:17:50 +02:00
|
|
|
let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE);
|
|
|
|
|
|
|
|
// Control interface
|
2022-04-23 01:29:19 +02:00
|
|
|
let mut iface = func.interface();
|
|
|
|
iface.handler(control);
|
2022-04-15 23:17:50 +02:00
|
|
|
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(
|
2022-03-30 02:01:09 +02:00
|
|
|
CS_INTERFACE,
|
|
|
|
&[
|
|
|
|
CDC_TYPE_HEADER, // bDescriptorSubtype
|
|
|
|
0x10,
|
|
|
|
0x01, // bcdCDC (1.10)
|
|
|
|
],
|
|
|
|
);
|
2022-04-15 23:17:50 +02:00
|
|
|
alt.descriptor(
|
2022-03-30 02:01:09 +02:00
|
|
|
CS_INTERFACE,
|
|
|
|
&[
|
|
|
|
CDC_TYPE_ACM, // bDescriptorSubtype
|
2022-12-26 09:36:04 +01:00
|
|
|
0x02, // bmCapabilities:
|
|
|
|
// D1: Device supports the request combination of
|
|
|
|
// Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding,
|
|
|
|
// and the Notification Serial_State.
|
2022-03-30 02:01:09 +02:00
|
|
|
],
|
|
|
|
);
|
2022-04-15 23:17:50 +02:00
|
|
|
alt.descriptor(
|
2022-03-30 02:01:09 +02:00
|
|
|
CS_INTERFACE,
|
|
|
|
&[
|
|
|
|
CDC_TYPE_UNION, // bDescriptorSubtype
|
|
|
|
comm_if.into(), // bControlInterface
|
|
|
|
data_if.into(), // bSubordinateInterface
|
|
|
|
],
|
|
|
|
);
|
2022-03-09 01:34:35 +01:00
|
|
|
|
2022-04-15 23:17:50 +02:00
|
|
|
let comm_ep = alt.endpoint_interrupt_in(8, 255);
|
|
|
|
|
|
|
|
// Data interface
|
2022-04-23 01:29:19 +02:00
|
|
|
let mut iface = func.interface();
|
2022-04-15 23:17:50 +02:00
|
|
|
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);
|
2022-03-09 01:34:35 +01:00
|
|
|
|
|
|
|
CdcAcmClass {
|
2022-03-30 02:16:34 +02:00
|
|
|
_comm_ep: comm_ep,
|
|
|
|
_data_if: data_if,
|
2022-03-09 01:34:35 +01:00
|
|
|
read_ep,
|
|
|
|
write_ep,
|
2022-03-28 02:20:01 +02:00
|
|
|
control: control_shared,
|
2022-03-09 01:34:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2022-03-28 02:20:01 +02:00
|
|
|
pub fn line_coding(&self) -> LineCoding {
|
|
|
|
self.control.line_coding.lock(|x| x.get())
|
2022-03-09 01:34:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the DTR (data terminal ready) state
|
|
|
|
pub fn dtr(&self) -> bool {
|
2022-03-28 02:20:01 +02:00
|
|
|
self.control.dtr.load(Ordering::Relaxed)
|
2022-03-09 01:34:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the RTS (request to send) state
|
|
|
|
pub fn rts(&self) -> bool {
|
2022-03-28 02:20:01 +02:00
|
|
|
self.control.rts.load(Ordering::Relaxed)
|
2022-03-09 01:34:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Writes a single packet into the IN endpoint.
|
2022-04-06 04:04:11 +02:00
|
|
|
pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
|
2022-03-09 01:34:35 +01:00
|
|
|
self.write_ep.write(data).await
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads a single packet from the OUT endpoint.
|
2022-04-06 04:04:11 +02:00
|
|
|
pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> {
|
2022-03-09 01:34:35 +01:00
|
|
|
self.read_ep.read(data).await
|
|
|
|
}
|
2022-04-02 22:35:03 +02:00
|
|
|
|
|
|
|
/// Waits for the USB host to enable this interface
|
|
|
|
pub async fn wait_connection(&mut self) {
|
|
|
|
self.read_ep.wait_enabled().await
|
|
|
|
}
|
2022-03-09 01:34:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Number of stop bits for LineCoding
|
2022-09-24 18:42:06 +02:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
2022-06-13 14:45:49 +02:00
|
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
2022-03-09 01:34:35 +01:00
|
|
|
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
|
2022-09-24 18:42:06 +02:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
2022-06-13 14:45:49 +02:00
|
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
2022-03-09 01:34:35 +01:00
|
|
|
pub enum ParityType {
|
|
|
|
None = 0,
|
|
|
|
Odd = 1,
|
2022-07-12 05:29:01 +02:00
|
|
|
Even = 2,
|
2022-03-09 01:34:35 +01:00
|
|
|
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.
|
2022-09-24 18:42:06 +02:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
2022-06-13 14:45:49 +02:00
|
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
2022-03-09 01:34:35 +01:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|