From e9219405ca04e23b6543fb841fd97df54cf72f94 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 7 Dec 2022 16:03:03 +0100 Subject: usb/cdc-ncm: add embassy-net Device implementation. --- embassy-usb/src/class/cdc_ncm.rs | 478 -------------------------- embassy-usb/src/class/cdc_ncm/embassy_net.rs | 449 ++++++++++++++++++++++++ embassy-usb/src/class/cdc_ncm/mod.rs | 496 +++++++++++++++++++++++++++ 3 files changed, 945 insertions(+), 478 deletions(-) delete mode 100644 embassy-usb/src/class/cdc_ncm.rs create mode 100644 embassy-usb/src/class/cdc_ncm/embassy_net.rs create mode 100644 embassy-usb/src/class/cdc_ncm/mod.rs (limited to 'embassy-usb/src') diff --git a/embassy-usb/src/class/cdc_ncm.rs b/embassy-usb/src/class/cdc_ncm.rs deleted file mode 100644 index a39b87e9b..000000000 --- a/embassy-usb/src/class/cdc_ncm.rs +++ /dev/null @@ -1,478 +0,0 @@ -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(buf: &mut [u8], data: T) -> &[u8] { - let len = size_of::(); - unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) } - &buf[..len] -} - -pub struct State<'a> { - comm_control: MaybeUninit>, - data_control: MaybeUninit, - 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::() 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 { - // 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(()) - } -} diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs new file mode 100644 index 000000000..60bbfd8d4 --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -0,0 +1,449 @@ +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::task::Context; + +use embassy_futures::select::{select, Either}; +use embassy_net::device::{Device as DeviceTrait, DeviceCapabilities, LinkState, Medium}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_usb_driver::Driver; + +use super::{CdcNcmClass, Receiver, Sender}; + +pub struct State<'d, const MTU: usize, const N_RX: usize, const N_TX: usize> { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl<'d, const MTU: usize, const N_RX: usize, const N_TX: usize> State<'d, MTU, N_RX, N_TX> { + const NEW_PACKET: PacketBuf = PacketBuf::new(); + + pub const fn new() -> Self { + Self { + rx: [Self::NEW_PACKET; N_RX], + tx: [Self::NEW_PACKET; N_TX], + inner: MaybeUninit::uninit(), + } + } +} + +struct StateInner<'d, const MTU: usize> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + link_state: Mutex>, +} + +/// State of the LinkState +struct LinkStateState { + state: LinkState, + waker: WakerRegistration, +} + +pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { + tx_usb: Sender<'d, D>, + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + rx_usb: Receiver<'d, D>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + link_state: &'d Mutex>, +} + +impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + pub async fn run(mut self) -> ! { + let rx_fut = async move { + loop { + trace!("WAITING for connection"); + self.link_state.lock(|s| { + let s = &mut *s.borrow_mut(); + s.state = LinkState::Down; + s.waker.wake(); + }); + + self.rx_usb.wait_connection().await.unwrap(); + + trace!("Connected"); + self.link_state.lock(|s| { + let s = &mut *s.borrow_mut(); + s.state = LinkState::Up; + s.waker.wake(); + }); + + loop { + let p = self.rx_chan.send().await; + match self.rx_usb.read_packet(&mut p.buf).await { + Ok(n) => { + p.len = n; + self.rx_chan.send_done(); + } + Err(e) => { + warn!("error reading packet: {:?}", e); + break; + } + }; + } + } + }; + let tx_fut = async move { + loop { + let p = self.tx_chan.recv().await; + if let Err(e) = self.tx_usb.write_packet(&p.buf[..p.len]).await { + warn!("Failed to TX packet: {:?}", e); + } + self.tx_chan.recv_done(); + } + }; + match select(rx_fut, tx_fut).await { + Either::First(x) => x, + Either::Second(x) => x, + } + } +} + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + pub fn into_embassy_net_device( + self, + state: &'d mut State<'d, MTU, N_RX, N_TX>, + ethernet_address: [u8; 6], + ) -> (Runner<'d, D, MTU>, Device<'d, MTU>) { + let (tx_usb, rx_usb) = self.split(); + + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header + caps.medium = Medium::Ethernet; + + let state = state.inner.write(StateInner { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + link_state: Mutex::new(RefCell::new(LinkStateState { + state: LinkState::Down, + waker: WakerRegistration::new(), + })), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + Runner { + tx_usb, + tx_chan: tx_receiver, + rx_usb, + rx_chan: rx_sender, + link_state: &state.link_state, + }, + Device { + caps, + ethernet_address, + link_state: &state.link_state, + rx: rx_receiver, + tx: tx_sender, + }, + ) + } +} + +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + pub const fn new() -> Self { + Self { len: 0, buf: [0; MTU] } + } +} + +pub struct Device<'d, const MTU: usize> { + rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + link_state: &'d Mutex>, + caps: DeviceCapabilities, + ethernet_address: [u8; 6], +} + +impl<'d, const MTU: usize> DeviceTrait for Device<'d, MTU> { + type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; + type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() { + Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) + } else { + None + } + } + + /// Construct a transmit token. + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.tx.poll_send(cx).is_ready() { + Some(TxToken { tx: self.tx.borrow() }) + } else { + None + } + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> DeviceCapabilities { + self.caps.clone() + } + + fn ethernet_address(&self) -> [u8; 6] { + self.ethernet_address + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.link_state.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.state + }) + } +} + +pub struct RxToken<'a, const MTU: usize> { + rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net::device::RxToken for RxToken<'a, MTU> { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.try_recv()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.recv_done(); + r + } +} + +pub struct TxToken<'a, const MTU: usize> { + tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net::device::TxToken for TxToken<'a, MTU> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.try_send()); + let r = f(&mut pkt.buf[..len]); + pkt.len = len; + self.tx.send_done(); + r + } +} + +mod zerocopy_channel { + use core::cell::RefCell; + use core::future::poll_fn; + use core::marker::PhantomData; + use core::task::{Context, Poll}; + + use embassy_sync::blocking_mutex::raw::RawMutex; + use embassy_sync::blocking_mutex::Mutex; + use embassy_sync::waitqueue::WakerRegistration; + + pub struct Channel<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex>, + } + + impl<'a, M: RawMutex, T> Channel<'a, M, T> { + pub fn new(buf: &'a mut [T]) -> Self { + let len = buf.len(); + assert!(len != 0); + + Self { + buf: buf.as_mut_ptr(), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + recv_waker: WakerRegistration::new(), + })), + } + } + + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + } + + pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Sender<'a, M, T> { + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { channel: self.channel } + } + + pub fn try_send(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn send(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(i), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + } + pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { channel: self.channel } + } + + pub fn try_recv(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn recv(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(i), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn recv_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + } + + struct State { + len: usize, + + /// Front index. Always 0..=(N-1) + front: usize, + /// Back index. Always 0..=(N-1). + back: usize, + + /// Used to distinguish "empty" and "full" cases when `front == back`. + /// May only be `true` if `front == back`, always `false` otherwise. + full: bool, + + send_waker: WakerRegistration, + recv_waker: WakerRegistration, + } + + impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.len { + 0 + } else { + i + 1 + } + } + + fn is_full(&self) -> bool { + self.full + } + + fn is_empty(&self) -> bool { + self.front == self.back && !self.full + } + + fn push_index(&mut self) -> Option { + match self.is_full() { + true => None, + false => Some(self.back), + } + } + + fn push_done(&mut self) { + assert!(!self.is_full()); + self.back = self.increment(self.back); + if self.back == self.front { + self.full = true; + } + self.send_waker.wake(); + } + + fn pop_index(&mut self) -> Option { + match self.is_empty() { + true => None, + false => Some(self.front), + } + } + + fn pop_done(&mut self) { + assert!(!self.is_empty()); + self.front = self.increment(self.front); + self.full = false; + self.recv_waker.wake(); + } + } +} diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs new file mode 100644 index 000000000..2ee47f68c --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -0,0 +1,496 @@ +/// CDC-NCM, aka Ethernet over USB. +/// +/// # Compatibility +/// +/// Windows: NOT supported in Windows 10. Supported in Windows 11. +/// +/// Linux: Well-supported since forever. +/// +/// Android: Support for CDC-NCM is spotty and varies across manufacturers. +/// +/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 +/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 +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; + +#[cfg(feature = "embassy-net")] +pub mod embassy_net; + +/// 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(buf: &mut [u8], data: T) -> &[u8] { + let len = size_of::(); + unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) } + &buf[..len] +} + +pub struct State<'a> { + comm_control: MaybeUninit>, + data_control: MaybeUninit, + 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::() 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 { + // 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(()) + } +} -- cgit