use core::convert::TryInto;
use core::mem;
use usb_device::class_prelude::*;
use usb_device::descriptor::lang_id::LangID;
use usb_device::device::DEFAULT_ALTERNATE_SETTING;
use usb_device::Result;
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 CdcAcmClass<'a, B: UsbBus> {
comm_if: InterfaceNumber,
comm_if_name: Option<(StringIndex, &'static str)>,
comm_ep: EndpointIn<'a, B>,
data_if: InterfaceNumber,
data_if_name: Option<(StringIndex, &'static str)>,
read_ep: EndpointOut<'a, B>,
write_ep: EndpointIn<'a, B>,
line_coding: LineCoding,
dtr: bool,
rts: bool,
}
impl<'a, B: UsbBus> CdcAcmClass<'a, B> {
pub fn new<'alloc: 'a>(
alloc: &'alloc UsbBusAllocator<B>,
max_packet_size: u16,
) -> CdcAcmClass<'a, B> {
Self::new_with_interface_names(alloc, max_packet_size, None, None)
}
pub fn new_with_interface_names<'alloc: 'a>(
alloc: &'alloc UsbBusAllocator<B>,
max_packet_size: u16,
comm_if_name: Option<&'static str>,
data_if_name: Option<&'static str>,
) -> CdcAcmClass<'a, B> {
let comm_if_name = comm_if_name.map(|s| (alloc.string(), s));
let data_if_name = data_if_name.map(|s| (alloc.string(), s));
CdcAcmClass {
comm_if: alloc.interface(),
comm_if_name,
comm_ep: alloc.interrupt(8, 255),
data_if: alloc.interface(),
data_if_name,
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: 9_600,
},
dtr: false,
rts: false,
}
}
pub fn max_packet_size(&self) -> u16 {
self.read_ep.max_packet_size()
}
pub fn line_coding(&self) -> &LineCoding {
&self.line_coding
}
pub fn dtr(&self) -> bool {
self.dtr
}
pub fn rts(&self) -> bool {
self.rts
}
pub fn write_packet(&mut self, data: &[u8]) -> Result<usize> {
self.write_ep.write(data)
}
pub fn read_packet(&mut self, data: &mut [u8]) -> Result<usize> {
self.read_ep.read(data)
}
pub fn write_ep(&self) -> &EndpointIn<'a, B> {
&self.write_ep
}
pub fn write_ep_mut(&mut self) -> &mut EndpointIn<'a, B> {
&mut self.write_ep
}
pub fn read_ep(&self) -> &EndpointOut<'a, B> {
&self.read_ep
}
pub fn read_ep_mut(&mut self) -> &mut EndpointOut<'a, B> {
&mut self.read_ep
}
}
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,
None,
)?;
writer.interface_alt(
self.comm_if,
DEFAULT_ALTERNATE_SETTING,
USB_CLASS_CDC,
CDC_SUBCLASS_ACM,
CDC_PROTOCOL_NONE,
self.comm_if_name.map(|n| n.0),
)?;
writer.write(
CS_INTERFACE,
&[
CDC_TYPE_HEADER, 0x10,
0x01, ],
)?;
writer.write(
CS_INTERFACE,
&[
CDC_TYPE_ACM, 0x00, ],
)?;
writer.write(
CS_INTERFACE,
&[
CDC_TYPE_UNION, self.comm_if.into(), self.data_if.into(), ],
)?;
writer.write(
CS_INTERFACE,
&[
CDC_TYPE_CALL_MANAGEMENT, 0x00, self.data_if.into(), ],
)?;
writer.endpoint(&self.comm_ep)?;
writer.interface_alt(
self.data_if,
DEFAULT_ALTERNATE_SETTING,
USB_CLASS_CDC_DATA,
0x00,
0x00,
self.data_if_name.map(|n| n.0),
)?;
writer.endpoint(&self.write_ep)?;
writer.endpoint(&self.read_ep)?;
Ok(())
}
fn get_string(&self, index: StringIndex, _lang_id: LangID) -> Option<&str> {
match (self.comm_if_name, self.data_if_name) {
(Some((i, s)), _) if i == index => Some(s),
(_, Some((i, s))) if i == index => Some(s),
_ => None,
}
}
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_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 => {
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();
}
};
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum StopBits {
One = 0,
OnePointFive = 1,
Two = 2,
}
impl From<u8> for StopBits {
fn from(value: u8) -> Self {
if value <= 2 {
unsafe { mem::transmute(value) }
} else {
StopBits::One
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
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
}
}
}
pub struct LineCoding {
stop_bits: StopBits,
data_bits: u8,
parity_type: ParityType,
data_rate: u32,
}
impl LineCoding {
pub fn stop_bits(&self) -> StopBits {
self.stop_bits
}
pub fn data_bits(&self) -> u8 {
self.data_bits
}
pub fn parity_type(&self) -> ParityType {
self.parity_type
}
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: 9_600,
}
}
}