339 lines
9.9 KiB
Rust
339 lines
9.9 KiB
Rust
|
// Copied from https://github.com/mvirkkunen/usbd-serial
|
||
|
#![allow(dead_code)]
|
||
|
|
||
|
use core::convert::TryInto;
|
||
|
use core::mem;
|
||
|
use usb_device::class_prelude::*;
|
||
|
use usb_device::Result;
|
||
|
|
||
|
/// This should be used as `device_class` when building the `UsbDevice`.
|
||
|
pub const USB_CLASS_CDC: u8 = 0x02;
|
||
|
|
||
|
const USB_CLASS_CDC_DATA: u8 = 0x0a;
|
||
|
const CDC_SUBCLASS_ACM: u8 = 0x02;
|
||
|
const CDC_PROTOCOL_NONE: u8 = 0x00;
|
||
|
|
||
|
const CS_INTERFACE: u8 = 0x24;
|
||
|
const CDC_TYPE_HEADER: u8 = 0x00;
|
||
|
const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
|
||
|
const CDC_TYPE_ACM: u8 = 0x02;
|
||
|
const CDC_TYPE_UNION: u8 = 0x06;
|
||
|
|
||
|
const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
|
||
|
#[allow(unused)]
|
||
|
const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
|
||
|
const REQ_SET_LINE_CODING: u8 = 0x20;
|
||
|
const REQ_GET_LINE_CODING: u8 = 0x21;
|
||
|
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
|
||
|
|
||
|
/// Packet level implementation of a CDC-ACM serial port.
|
||
|
///
|
||
|
/// This class can be used directly and it has the least overhead due to directly reading and
|
||
|
/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
|
||
|
/// port. The following constraints must be followed if you use this class directly:
|
||
|
///
|
||
|
/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the
|
||
|
/// method will return a `WouldBlock` error if there is no packet to be read.
|
||
|
/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the
|
||
|
/// method will return a `WouldBlock` error if the previous packet has not been sent yet.
|
||
|
/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
|
||
|
/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
|
||
|
/// can be sent if there is no other data to send. This is because USB bulk transactions must be
|
||
|
/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
|
||
|
pub struct CdcAcmClass<'a, B: UsbBus> {
|
||
|
comm_if: InterfaceNumber,
|
||
|
comm_ep: EndpointIn<'a, B>,
|
||
|
data_if: InterfaceNumber,
|
||
|
read_ep: EndpointOut<'a, B>,
|
||
|
write_ep: EndpointIn<'a, B>,
|
||
|
line_coding: LineCoding,
|
||
|
dtr: bool,
|
||
|
rts: bool,
|
||
|
}
|
||
|
|
||
|
impl<B: UsbBus> CdcAcmClass<'_, B> {
|
||
|
/// 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(alloc: &UsbBusAllocator<B>, max_packet_size: u16) -> CdcAcmClass<'_, B> {
|
||
|
CdcAcmClass {
|
||
|
comm_if: alloc.interface(),
|
||
|
comm_ep: alloc.interrupt(8, 255),
|
||
|
data_if: alloc.interface(),
|
||
|
read_ep: alloc.bulk(max_packet_size),
|
||
|
write_ep: alloc.bulk(max_packet_size),
|
||
|
line_coding: LineCoding {
|
||
|
stop_bits: StopBits::One,
|
||
|
data_bits: 8,
|
||
|
parity_type: ParityType::None,
|
||
|
data_rate: 8_000,
|
||
|
},
|
||
|
dtr: false,
|
||
|
rts: false,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Gets the maximum packet size in bytes.
|
||
|
pub fn max_packet_size(&self) -> u16 {
|
||
|
// The size is the same for both endpoints.
|
||
|
self.read_ep.max_packet_size()
|
||
|
}
|
||
|
|
||
|
/// Gets the current line coding. The line coding contains information that's mainly relevant
|
||
|
/// for USB to UART serial port emulators, and can be ignored if not relevant.
|
||
|
pub fn line_coding(&self) -> &LineCoding {
|
||
|
&self.line_coding
|
||
|
}
|
||
|
|
||
|
/// Gets the DTR (data terminal ready) state
|
||
|
pub fn dtr(&self) -> bool {
|
||
|
self.dtr
|
||
|
}
|
||
|
|
||
|
/// Gets the RTS (request to send) state
|
||
|
pub fn rts(&self) -> bool {
|
||
|
self.rts
|
||
|
}
|
||
|
|
||
|
/// Writes a single packet into the IN endpoint.
|
||
|
pub fn write_packet(&mut self, data: &[u8]) -> Result<usize> {
|
||
|
self.write_ep.write(data)
|
||
|
}
|
||
|
|
||
|
/// Reads a single packet from the OUT endpoint.
|
||
|
pub fn read_packet(&mut self, data: &mut [u8]) -> Result<usize> {
|
||
|
self.read_ep.read(data)
|
||
|
}
|
||
|
|
||
|
/// Gets the address of the IN endpoint.
|
||
|
pub fn write_ep_address(&self) -> EndpointAddress {
|
||
|
self.write_ep.address()
|
||
|
}
|
||
|
|
||
|
/// Gets the address of the OUT endpoint.
|
||
|
pub fn read_ep_address(&self) -> EndpointAddress {
|
||
|
self.read_ep.address()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
|
||
|
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
|
||
|
writer.iad(
|
||
|
self.comm_if,
|
||
|
2,
|
||
|
USB_CLASS_CDC,
|
||
|
CDC_SUBCLASS_ACM,
|
||
|
CDC_PROTOCOL_NONE,
|
||
|
)?;
|
||
|
|
||
|
writer.interface(
|
||
|
self.comm_if,
|
||
|
USB_CLASS_CDC,
|
||
|
CDC_SUBCLASS_ACM,
|
||
|
CDC_PROTOCOL_NONE,
|
||
|
)?;
|
||
|
|
||
|
writer.write(
|
||
|
CS_INTERFACE,
|
||
|
&[
|
||
|
CDC_TYPE_HEADER, // bDescriptorSubtype
|
||
|
0x10,
|
||
|
0x01, // bcdCDC (1.10)
|
||
|
],
|
||
|
)?;
|
||
|
|
||
|
writer.write(
|
||
|
CS_INTERFACE,
|
||
|
&[
|
||
|
CDC_TYPE_ACM, // bDescriptorSubtype
|
||
|
0x00, // bmCapabilities
|
||
|
],
|
||
|
)?;
|
||
|
|
||
|
writer.write(
|
||
|
CS_INTERFACE,
|
||
|
&[
|
||
|
CDC_TYPE_UNION, // bDescriptorSubtype
|
||
|
self.comm_if.into(), // bControlInterface
|
||
|
self.data_if.into(), // bSubordinateInterface
|
||
|
],
|
||
|
)?;
|
||
|
|
||
|
writer.write(
|
||
|
CS_INTERFACE,
|
||
|
&[
|
||
|
CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
|
||
|
0x00, // bmCapabilities
|
||
|
self.data_if.into(), // bDataInterface
|
||
|
],
|
||
|
)?;
|
||
|
|
||
|
writer.endpoint(&self.comm_ep)?;
|
||
|
|
||
|
writer.interface(self.data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)?;
|
||
|
|
||
|
writer.endpoint(&self.write_ep)?;
|
||
|
writer.endpoint(&self.read_ep)?;
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn reset(&mut self) {
|
||
|
self.line_coding = LineCoding::default();
|
||
|
self.dtr = false;
|
||
|
self.rts = false;
|
||
|
}
|
||
|
|
||
|
fn control_in(&mut self, xfer: ControlIn<B>) {
|
||
|
let req = xfer.request();
|
||
|
|
||
|
if !(req.request_type == control::RequestType::Class
|
||
|
&& req.recipient == control::Recipient::Interface
|
||
|
&& req.index == u8::from(self.comm_if) as u16)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
match req.request {
|
||
|
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
|
||
|
REQ_GET_LINE_CODING if req.length == 7 => {
|
||
|
xfer.accept(|data| {
|
||
|
data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
|
||
|
data[4] = self.line_coding.stop_bits as u8;
|
||
|
data[5] = self.line_coding.parity_type as u8;
|
||
|
data[6] = self.line_coding.data_bits;
|
||
|
|
||
|
Ok(7)
|
||
|
})
|
||
|
.ok();
|
||
|
}
|
||
|
_ => {
|
||
|
xfer.reject().ok();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn control_out(&mut self, xfer: ControlOut<B>) {
|
||
|
let req = xfer.request();
|
||
|
|
||
|
if !(req.request_type == control::RequestType::Class
|
||
|
&& req.recipient == control::Recipient::Interface
|
||
|
&& req.index == u8::from(self.comm_if) as u16)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
match req.request {
|
||
|
REQ_SEND_ENCAPSULATED_COMMAND => {
|
||
|
// We don't actually support encapsulated commands but pretend we do for standards
|
||
|
// compatibility.
|
||
|
xfer.accept().ok();
|
||
|
}
|
||
|
REQ_SET_LINE_CODING if xfer.data().len() >= 7 => {
|
||
|
self.line_coding.data_rate =
|
||
|
u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap());
|
||
|
self.line_coding.stop_bits = xfer.data()[4].into();
|
||
|
self.line_coding.parity_type = xfer.data()[5].into();
|
||
|
self.line_coding.data_bits = xfer.data()[6];
|
||
|
|
||
|
xfer.accept().ok();
|
||
|
}
|
||
|
REQ_SET_CONTROL_LINE_STATE => {
|
||
|
self.dtr = (req.value & 0x0001) != 0;
|
||
|
self.rts = (req.value & 0x0002) != 0;
|
||
|
|
||
|
xfer.accept().ok();
|
||
|
}
|
||
|
_ => {
|
||
|
xfer.reject().ok();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Number of stop bits for LineCoding
|
||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||
|
pub enum StopBits {
|
||
|
/// 1 stop bit
|
||
|
One = 0,
|
||
|
|
||
|
/// 1.5 stop bits
|
||
|
OnePointFive = 1,
|
||
|
|
||
|
/// 2 stop bits
|
||
|
Two = 2,
|
||
|
}
|
||
|
|
||
|
impl From<u8> for StopBits {
|
||
|
fn from(value: u8) -> Self {
|
||
|
if value <= 2 {
|
||
|
unsafe { mem::transmute(value) }
|
||
|
} else {
|
||
|
StopBits::One
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Parity for LineCoding
|
||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||
|
pub enum ParityType {
|
||
|
None = 0,
|
||
|
Odd = 1,
|
||
|
Event = 2,
|
||
|
Mark = 3,
|
||
|
Space = 4,
|
||
|
}
|
||
|
|
||
|
impl From<u8> for ParityType {
|
||
|
fn from(value: u8) -> Self {
|
||
|
if value <= 4 {
|
||
|
unsafe { mem::transmute(value) }
|
||
|
} else {
|
||
|
ParityType::None
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Line coding parameters
|
||
|
///
|
||
|
/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
|
||
|
/// be ignored if you don't plan to interface with a physical UART.
|
||
|
pub struct LineCoding {
|
||
|
stop_bits: StopBits,
|
||
|
data_bits: u8,
|
||
|
parity_type: ParityType,
|
||
|
data_rate: u32,
|
||
|
}
|
||
|
|
||
|
impl LineCoding {
|
||
|
/// Gets the number of stop bits for UART communication.
|
||
|
pub fn stop_bits(&self) -> StopBits {
|
||
|
self.stop_bits
|
||
|
}
|
||
|
|
||
|
/// Gets the number of data bits for UART communication.
|
||
|
pub fn data_bits(&self) -> u8 {
|
||
|
self.data_bits
|
||
|
}
|
||
|
|
||
|
/// Gets the parity type for UART communication.
|
||
|
pub fn parity_type(&self) -> ParityType {
|
||
|
self.parity_type
|
||
|
}
|
||
|
|
||
|
/// Gets the data rate in bits per second for UART communication.
|
||
|
pub fn data_rate(&self) -> u32 {
|
||
|
self.data_rate
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Default for LineCoding {
|
||
|
fn default() -> Self {
|
||
|
LineCoding {
|
||
|
stop_bits: StopBits::One,
|
||
|
data_bits: 8,
|
||
|
parity_type: ParityType::None,
|
||
|
data_rate: 8_000,
|
||
|
}
|
||
|
}
|
||
|
}
|