use tock_registers::interfaces::Readable;
use crate::usb::device::desc::{encode_string_descriptor, EP_ATTR_BULK, EP_ATTR_INTERRUPT};
use crate::usb::device::ep0::{
configure_bulk_in_ep, configure_bulk_out_ep, configure_intr_in_ep, prime_bulk_out,
read_clear_diepint, read_clear_doepint, start_bulk_in, Ep0Reply, Setup,
};
use crate::usb::device::{Ep0Context, UsbDeviceClass, UsbSpeed};
use crate::utils::cache;
const BULK_DATA_EP: u8 = 1;
const NOTIFY_EP: u8 = 3;
const DEVICE_DESC: [u8; 18] = [
18, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 64, 0x09, 0x12, 0x01, 0x00, 0x01, 0x00, 1, 2, 3, 1, ];
const CONFIG_DESC: [u8; 67] = [
9, 0x02, 67, 0, 2, 1, 0, 0xC0, 50,
9, 0x04, 0, 0, 1, 0x02, 0x02, 0x00, 0,
5, 0x24, 0x00, 0x10, 0x01,
5, 0x24, 0x01, 0x00, 1,
4, 0x24, 0x02, 0x02,
5, 0x24, 0x06, 0, 1,
7, 0x05, 0x83, EP_ATTR_INTERRUPT, 8, 0, 16,
9, 0x04, 1, 0, 2, 0x0A, 0x00, 0x00, 0,
7, 0x05, 0x01, EP_ATTR_BULK, 64, 0, 0,
7, 0x05, 0x81, EP_ATTR_BULK, 64, 0, 0,
];
const STRING0: [u8; 4] = [4, 0x03, 0x09, 0x04];
static mut STRING_BUFS: [[u8; 64]; 4] = [[0; 64]; 4];
fn encode_string(idx: usize, s: &str) -> &'static [u8] {
let buf = unsafe { &mut STRING_BUFS[idx] };
let n = encode_string_descriptor(s, buf);
unsafe { core::slice::from_raw_parts(buf.as_ptr(), n) }
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct LineCoding {
dwdte_rate: u32,
b_char_format: u8,
b_parity_type: u8,
b_data_bits: u8,
}
impl LineCoding {
const DEFAULT: Self = Self {
dwdte_rate: 115_200,
b_char_format: 0,
b_parity_type: 0,
b_data_bits: 8,
};
}
const BULK_MPS: u32 = 64;
const BULK_BUF_SIZE: usize = 64;
#[repr(C, align(64))]
struct DmaBuf([u8; BULK_BUF_SIZE]);
pub struct CdcAcm {
line_coding: LineCoding,
control_lines: u16,
rx_buf: DmaBuf,
tx_buf: DmaBuf,
tx_pending: usize,
in_busy: bool,
out_armed: bool,
in_need_zlp: bool,
speed: UsbSpeed,
configured: bool,
}
impl CdcAcm {
pub const fn new() -> Self {
Self {
line_coding: LineCoding::DEFAULT,
control_lines: 0,
rx_buf: DmaBuf([0; BULK_BUF_SIZE]),
tx_buf: DmaBuf([0; BULK_BUF_SIZE]),
tx_pending: 0,
in_busy: false,
out_armed: false,
in_need_zlp: false,
speed: UsbSpeed::FullSpeed,
configured: false,
}
}
}
impl Default for CdcAcm {
fn default() -> Self {
Self::new()
}
}
const CDC_REQ_SET_LINE_CODING: u8 = 0x20;
const CDC_REQ_GET_LINE_CODING: u8 = 0x21;
const CDC_REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
const CDC_REQ_SEND_BREAK: u8 = 0x23;
impl UsbDeviceClass for CdcAcm {
fn device_descriptor(&self) -> &'static [u8] {
&DEVICE_DESC
}
fn config_descriptor(&self) -> &'static [u8] {
&CONFIG_DESC
}
fn string_descriptor(&self, idx: u8) -> Option<&'static [u8]> {
match idx {
0 => Some(&STRING0),
1 => Some(encode_string(1, "sg200x-bsp")),
2 => Some(encode_string(2, "SG2002 CDC-ACM")),
3 => Some(encode_string(3, "SN-2026")),
_ => None,
}
}
fn class_setup(&mut self, setup: &Setup, in_buf: &mut [u8]) -> Ep0Reply {
if setup.recipient() != crate::usb::device::desc::REQ_RCPT_INTERFACE {
return Ep0Reply::Stall;
}
match setup.b_request {
CDC_REQ_SET_LINE_CODING => {
if setup.dir_in() || setup.w_length as usize != 7 {
return Ep0Reply::Stall;
}
Ep0Reply::AcceptOut
}
CDC_REQ_GET_LINE_CODING => {
if !setup.dir_in() || setup.w_length < 7 {
return Ep0Reply::Stall;
}
let lc = self.line_coding;
in_buf[0] = (lc.dwdte_rate & 0xff) as u8;
in_buf[1] = ((lc.dwdte_rate >> 8) & 0xff) as u8;
in_buf[2] = ((lc.dwdte_rate >> 16) & 0xff) as u8;
in_buf[3] = ((lc.dwdte_rate >> 24) & 0xff) as u8;
in_buf[4] = lc.b_char_format;
in_buf[5] = lc.b_parity_type;
in_buf[6] = lc.b_data_bits;
Ep0Reply::Data(7)
}
CDC_REQ_SET_CONTROL_LINE_STATE => {
self.control_lines = setup.w_value;
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV CDC SET_CONTROL_LINE_STATE = {:#06x} (DTR={} RTS={})",
setup.w_value,
(setup.w_value >> 0) & 1,
(setup.w_value >> 1) & 1,
));
Ep0Reply::StatusOnly
}
CDC_REQ_SEND_BREAK => Ep0Reply::StatusOnly,
_ => Ep0Reply::Stall,
}
}
fn class_out_data(&mut self, setup: &Setup, data: &[u8]) {
if setup.b_request == CDC_REQ_SET_LINE_CODING && data.len() >= 7 {
let dwdte_rate = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
self.line_coding.dwdte_rate = dwdte_rate;
self.line_coding.b_char_format = data[4];
self.line_coding.b_parity_type = data[5];
self.line_coding.b_data_bits = data[6];
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV CDC SET_LINE_CODING rate={} fmt={} parity={} bits={}",
dwdte_rate, data[4], data[5], data[6]
));
}
}
fn on_configured(&mut self, cfg: u8, ctx: &Ep0Context) {
if cfg == 0 {
self.configured = false;
self.in_busy = false;
self.out_armed = false;
self.in_need_zlp = false;
self.tx_pending = 0;
return;
}
self.speed = ctx.speed;
let _ = configure_bulk_out_ep(BULK_DATA_EP, BULK_MPS);
let _ = configure_bulk_in_ep(BULK_DATA_EP, BULK_MPS, 1);
let _ = configure_intr_in_ep(NOTIFY_EP, 8, 2);
self.configured = true;
self.in_busy = false;
self.out_armed = false;
self.in_need_zlp = false;
self.tx_pending = 0;
crate::usb::log::usb_log_fmt(format_args!(
"USB-DEV CDC configured: bulk={} mps={} speed={:?}",
BULK_DATA_EP, BULK_MPS, ctx.speed
));
}
fn poll(&mut self, _ctx: &Ep0Context) {
if !self.configured {
return;
}
if self.in_busy {
let int = read_clear_diepint(BULK_DATA_EP);
if int & 0x1 != 0 {
self.in_busy = false;
if self.in_need_zlp {
self.in_need_zlp = false;
let pa = crate::usb::platform::usb_dma_phys_for(self.tx_buf.0.as_ptr());
if start_bulk_in(BULK_DATA_EP, pa, 0, 1).is_ok() {
self.in_busy = true;
}
}
}
}
if self.out_armed {
let int = read_clear_doepint(BULK_DATA_EP);
let xfercompl = int & 0x1 != 0;
let bbl = int & (1 << 12) != 0;
if xfercompl || bbl {
self.out_armed = false;
let original = BULK_BUF_SIZE as u32;
let r = crate::usb::host::dwc2::mmio::dwc2_regs().unwrap();
let residual = r.doep[BULK_DATA_EP as usize].doeptsiz.get() & 0x7ffff;
let mut actual = original.saturating_sub(residual) as usize;
if bbl && actual == 0 {
actual = BULK_BUF_SIZE;
}
if actual > 0 {
unsafe {
cache::dcache_invalidate_after_dma(
self.rx_buf.0.as_mut_ptr(),
BULK_BUF_SIZE,
);
}
let n = actual.min(BULK_BUF_SIZE);
self.tx_buf.0[..n].copy_from_slice(&self.rx_buf.0[..n]);
self.tx_pending = n;
self.try_echo();
}
}
}
if !self.out_armed && !self.in_busy && self.tx_pending == 0 {
unsafe {
cache::dcache_invalidate_after_dma(self.rx_buf.0.as_mut_ptr(), BULK_BUF_SIZE);
}
let pa = crate::usb::platform::usb_dma_phys_for(self.rx_buf.0.as_ptr());
let _ = prime_bulk_out(BULK_DATA_EP, pa, BULK_BUF_SIZE as u32, 1);
self.out_armed = true;
}
}
}
impl CdcAcm {
fn try_echo(&mut self) {
if self.in_busy || self.tx_pending == 0 {
return;
}
let n = self.tx_pending as u32;
unsafe {
cache::dcache_clean_for_dma(self.tx_buf.0.as_ptr(), self.tx_pending);
}
let pa = crate::usb::platform::usb_dma_phys_for(self.tx_buf.0.as_ptr());
let pkts = n.div_ceil(BULK_MPS).max(1);
if start_bulk_in(BULK_DATA_EP, pa, n, pkts).is_ok() {
self.in_busy = true;
self.in_need_zlp = n > 0 && n % BULK_MPS == 0;
self.tx_pending = 0;
}
}
}