Skip to main content

embassy_usb/class/cdc_ncm/
mod.rs

1//! CDC-NCM class implementation, aka Ethernet over USB.
2//!
3//! # Compatibility
4//!
5//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11.
6//!
7//! Linux: Well-supported since forever.
8//!
9//! Android: Support for CDC-NCM is spotty and varies across manufacturers.
10//!
11//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
12//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
13//!   it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
14//!   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>
15//!   and this nonsense in the linux kernel: <https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757>
16
17use core::mem::{MaybeUninit, size_of};
18use core::ptr::{addr_of, copy_nonoverlapping};
19
20use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType};
21use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
22use crate::types::{InterfaceNumber, StringIndex};
23use crate::{Builder, Handler};
24
25pub mod embassy_net;
26
27/// This should be used as `device_class` when building the `UsbDevice`.
28pub const USB_CLASS_CDC: u8 = 0x02;
29
30const USB_CLASS_CDC_DATA: u8 = 0x0a;
31const CDC_SUBCLASS_NCM: u8 = 0x0d;
32
33const CDC_PROTOCOL_NONE: u8 = 0x00;
34const CDC_PROTOCOL_NTB: u8 = 0x01;
35
36const CS_INTERFACE: u8 = 0x24;
37const CDC_TYPE_HEADER: u8 = 0x00;
38const CDC_TYPE_UNION: u8 = 0x06;
39const CDC_TYPE_ETHERNET: u8 = 0x0F;
40const CDC_TYPE_NCM: u8 = 0x1A;
41
42const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
43//const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
44//const REQ_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40;
45//const REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x41;
46//const REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x42;
47//const REQ_SET_ETHERNET_PACKET_FILTER: u8 = 0x43;
48//const REQ_GET_ETHERNET_STATISTIC: u8 = 0x44;
49const REQ_GET_NTB_PARAMETERS: u8 = 0x80;
50//const REQ_GET_NET_ADDRESS: u8 = 0x81;
51//const REQ_SET_NET_ADDRESS: u8 = 0x82;
52//const REQ_GET_NTB_FORMAT: u8 = 0x83;
53//const REQ_SET_NTB_FORMAT: u8 = 0x84;
54//const REQ_GET_NTB_INPUT_SIZE: u8 = 0x85;
55const REQ_SET_NTB_INPUT_SIZE: u8 = 0x86;
56//const REQ_GET_MAX_DATAGRAM_SIZE: u8 = 0x87;
57//const REQ_SET_MAX_DATAGRAM_SIZE: u8 = 0x88;
58//const REQ_GET_CRC_MODE: u8 = 0x89;
59//const REQ_SET_CRC_MODE: u8 = 0x8A;
60
61//const NOTIF_MAX_PACKET_SIZE: u16 = 8;
62//const NOTIF_POLL_INTERVAL: u8 = 20;
63
64const NTB_MAX_SIZE: usize = 2048;
65const SIG_NTH: u32 = 0x484d_434e;
66const SIG_NDP_NO_FCS: u32 = 0x304d_434e;
67const SIG_NDP_WITH_FCS: u32 = 0x314d_434e;
68
69const ALTERNATE_SETTING_DISABLED: u8 = 0x00;
70const ALTERNATE_SETTING_ENABLED: u8 = 0x01;
71
72/// Simple NTB header (NTH+NDP all in one) for sending packets
73#[repr(packed)]
74#[allow(unused)]
75struct NtbOutHeader {
76    // NTH
77    nth_sig: u32,
78    nth_len: u16,
79    nth_seq: u16,
80    nth_total_len: u16,
81    nth_first_index: u16,
82
83    // NDP
84    ndp_sig: u32,
85    ndp_len: u16,
86    ndp_next_index: u16,
87    ndp_datagram_index: u16,
88    ndp_datagram_len: u16,
89    ndp_term1: u16,
90    ndp_term2: u16,
91}
92
93#[repr(packed)]
94#[allow(unused)]
95struct NtbParameters {
96    length: u16,
97    formats_supported: u16,
98    in_params: NtbParametersDir,
99    out_params: NtbParametersDir,
100}
101
102#[repr(packed)]
103#[allow(unused)]
104struct NtbParametersDir {
105    max_size: u32,
106    divisor: u16,
107    payload_remainder: u16,
108    out_alignment: u16,
109    max_datagram_count: u16,
110}
111
112fn byteify<T>(buf: &mut [u8], data: T) -> &[u8] {
113    let len = size_of::<T>();
114    unsafe { copy_nonoverlapping(addr_of!(data).cast(), buf.as_mut_ptr(), len) }
115    &buf[..len]
116}
117
118/// Internal state for the CDC-NCM class.
119pub struct State<'a> {
120    control: MaybeUninit<Control<'a>>,
121    shared: ControlShared,
122}
123
124impl<'a> Default for State<'a> {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130impl<'a> State<'a> {
131    /// Create a new `State`.
132    pub fn new() -> Self {
133        Self {
134            control: MaybeUninit::uninit(),
135            shared: ControlShared::default(),
136        }
137    }
138}
139
140/// Shared data between Control and `CdcAcmClass`
141#[derive(Default)]
142struct ControlShared {
143    mac_addr: [u8; 6],
144}
145
146struct Control<'a> {
147    mac_addr_string: StringIndex,
148    shared: &'a ControlShared,
149    mac_addr_str: [u8; 12],
150    comm_if: InterfaceNumber,
151    data_if: InterfaceNumber,
152}
153
154impl<'d> Handler for Control<'d> {
155    fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) {
156        if iface != self.data_if {
157            return;
158        }
159
160        match alternate_setting {
161            ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"),
162            ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"),
163            _ => unreachable!(),
164        }
165    }
166
167    fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option<OutResponse> {
168        if (req.request_type, req.recipient, req.index)
169            != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
170        {
171            return None;
172        }
173
174        match req.request {
175            REQ_SEND_ENCAPSULATED_COMMAND => {
176                // We don't actually support encapsulated commands but pretend we do for standards
177                // compatibility.
178                Some(OutResponse::Accepted)
179            }
180            REQ_SET_NTB_INPUT_SIZE => {
181                // TODO
182                Some(OutResponse::Accepted)
183            }
184            _ => Some(OutResponse::Rejected),
185        }
186    }
187
188    fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
189        if (req.request_type, req.recipient, req.index)
190            != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
191        {
192            return None;
193        }
194
195        match req.request {
196            REQ_GET_NTB_PARAMETERS => {
197                let res = NtbParameters {
198                    length: size_of::<NtbParameters>() as _,
199                    formats_supported: 1, // only 16bit,
200                    in_params: NtbParametersDir {
201                        max_size: NTB_MAX_SIZE as _,
202                        divisor: 4,
203                        payload_remainder: 0,
204                        out_alignment: 4,
205                        max_datagram_count: 0, // not used
206                    },
207                    out_params: NtbParametersDir {
208                        max_size: NTB_MAX_SIZE as _,
209                        divisor: 4,
210                        payload_remainder: 0,
211                        out_alignment: 4,
212                        max_datagram_count: 1, // We only decode 1 packet per NTB
213                    },
214                };
215                Some(InResponse::Accepted(byteify(buf, res)))
216            }
217            _ => Some(InResponse::Rejected),
218        }
219    }
220
221    fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> {
222        if index == self.mac_addr_string {
223            let mac_addr = self.shared.mac_addr;
224            let s = &mut self.mac_addr_str;
225            for i in 0..12 {
226                let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF;
227                s[i] = match n {
228                    0x0..=0x9 => b'0' + n,
229                    0xA..=0xF => b'A' + n - 0xA,
230                    _ => unreachable!(),
231                }
232            }
233
234            Some(unsafe { core::str::from_utf8_unchecked(s) })
235        } else {
236            warn!("unknown string index requested");
237            None
238        }
239    }
240}
241
242/// CDC-NCM class
243pub struct CdcNcmClass<'d, D: Driver<'d>> {
244    _comm_if: InterfaceNumber,
245    comm_ep: D::EndpointIn,
246
247    data_if: InterfaceNumber,
248    read_ep: D::EndpointOut,
249    write_ep: D::EndpointIn,
250
251    _control: &'d ControlShared,
252
253    max_packet_size: usize,
254}
255
256impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
257    /// Create a new CDC NCM class.
258    pub fn new(
259        builder: &mut Builder<'d, D>,
260        state: &'d mut State<'d>,
261        mac_address: [u8; 6],
262        max_packet_size: u16,
263    ) -> Self {
264        state.shared.mac_addr = mac_address;
265
266        let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE);
267
268        // Control interface
269        let mut iface = func.interface();
270        let mac_addr_string = iface.string();
271        let comm_if = iface.interface_number();
272        let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None);
273
274        alt.descriptor(
275            CS_INTERFACE,
276            &[
277                CDC_TYPE_HEADER, // bDescriptorSubtype
278                0x10,
279                0x01, // bcdCDC (1.10)
280            ],
281        );
282        alt.descriptor(
283            CS_INTERFACE,
284            &[
285                CDC_TYPE_UNION,        // bDescriptorSubtype
286                comm_if.into(),        // bControlInterface
287                u8::from(comm_if) + 1, // bSubordinateInterface
288            ],
289        );
290        alt.descriptor(
291            CS_INTERFACE,
292            &[
293                CDC_TYPE_ETHERNET,      // bDescriptorSubtype
294                mac_addr_string.into(), // iMACAddress
295                0,                      // bmEthernetStatistics
296                0,                      // |
297                0,                      // |
298                0,                      // |
299                0xea,                   // wMaxSegmentSize = 1514
300                0x05,                   // |
301                0,                      // wNumberMCFilters
302                0,                      // |
303                0,                      // bNumberPowerFilters
304            ],
305        );
306        alt.descriptor(
307            CS_INTERFACE,
308            &[
309                CDC_TYPE_NCM, // bDescriptorSubtype
310                0x00,         // bcdNCMVersion
311                0x01,         // |
312                0,            // bmNetworkCapabilities
313            ],
314        );
315
316        let comm_ep = alt.endpoint_interrupt_in(None, 8, 255);
317
318        // Data interface
319        let mut iface = func.interface();
320        let data_if = iface.interface_number();
321        let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None);
322        let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None);
323        let read_ep = alt.endpoint_bulk_out(None, max_packet_size);
324        let write_ep = alt.endpoint_bulk_in(None, max_packet_size);
325
326        drop(func);
327
328        let control = state.control.write(Control {
329            mac_addr_string,
330            shared: &state.shared,
331            mac_addr_str: [0; 12],
332            comm_if,
333            data_if,
334        });
335        builder.handler(control);
336
337        CdcNcmClass {
338            _comm_if: comm_if,
339            comm_ep,
340            data_if,
341            read_ep,
342            write_ep,
343            _control: &state.shared,
344            max_packet_size: max_packet_size as usize,
345        }
346    }
347
348    /// Split the class into a sender and receiver.
349    ///
350    /// This allows concurrently sending and receiving packets from separate tasks.
351    pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
352        (
353            Sender {
354                write_ep: self.write_ep,
355                seq: 0,
356                max_packet_size: self.max_packet_size,
357            },
358            Receiver {
359                data_if: self.data_if,
360                comm_ep: self.comm_ep,
361                read_ep: self.read_ep,
362            },
363        )
364    }
365}
366
367/// CDC NCM class packet sender.
368///
369/// You can obtain a `Sender` with [`CdcNcmClass::split`]
370pub struct Sender<'d, D: Driver<'d>> {
371    write_ep: D::EndpointIn,
372    seq: u16,
373    max_packet_size: usize,
374}
375
376impl<'d, D: Driver<'d>> Sender<'d, D> {
377    /// Write a packet.
378    ///
379    /// This waits until the packet is successfully stored in the CDC-NCM endpoint buffers.
380    pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
381        const OUT_HEADER_LEN: usize = 28;
382        const ABS_MAX_PACKET_SIZE: usize = 512;
383
384        let seq = self.seq;
385        self.seq = self.seq.wrapping_add(1);
386
387        let header = NtbOutHeader {
388            nth_sig: SIG_NTH,
389            nth_len: 0x0c,
390            nth_seq: seq,
391            nth_total_len: (data.len() + OUT_HEADER_LEN) as u16,
392            nth_first_index: 0x0c,
393
394            ndp_sig: SIG_NDP_NO_FCS,
395            ndp_len: 0x10,
396            ndp_next_index: 0x00,
397            ndp_datagram_index: OUT_HEADER_LEN as u16,
398            ndp_datagram_len: data.len() as u16,
399            ndp_term1: 0x00,
400            ndp_term2: 0x00,
401        };
402
403        // Build first packet on a buffer, send next packets straight from `data`.
404        let mut buf = [0; ABS_MAX_PACKET_SIZE];
405        let n = byteify(&mut buf, header);
406        assert_eq!(n.len(), OUT_HEADER_LEN);
407
408        if OUT_HEADER_LEN + data.len() < self.max_packet_size {
409            // First packet is not full, just send it.
410            // No need to send ZLP because it's short for sure.
411            buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data);
412            self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?;
413        } else {
414            let (d1, d2) = data.split_at(self.max_packet_size - OUT_HEADER_LEN);
415
416            buf[OUT_HEADER_LEN..self.max_packet_size].copy_from_slice(d1);
417            self.write_ep.write(&buf[..self.max_packet_size]).await?;
418
419            for chunk in d2.chunks(self.max_packet_size) {
420                self.write_ep.write(chunk).await?;
421            }
422
423            // Send ZLP if needed.
424            if d2.len() % self.max_packet_size == 0 {
425                self.write_ep.write(&[]).await?;
426            }
427        }
428
429        Ok(())
430    }
431}
432
433/// CDC NCM class packet receiver.
434///
435/// You can obtain a `Receiver` with [`CdcNcmClass::split`]
436pub struct Receiver<'d, D: Driver<'d>> {
437    data_if: InterfaceNumber,
438    comm_ep: D::EndpointIn,
439    read_ep: D::EndpointOut,
440}
441
442impl<'d, D: Driver<'d>> Receiver<'d, D> {
443    /// Write a network packet.
444    ///
445    /// This waits until a packet is successfully received from the endpoint buffers.
446    pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
447        // Retry loop
448        loop {
449            // read NTB
450            let mut ntb = [0u8; NTB_MAX_SIZE];
451            let mut pos = 0;
452            loop {
453                let n = self.read_ep.read(&mut ntb[pos..]).await?;
454                pos += n;
455                if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE {
456                    break;
457                }
458            }
459
460            let ntb = &ntb[..pos];
461
462            // Process NTB header (NTH)
463            let Some(nth) = ntb.get(..12) else {
464                warn!("Received too short NTB");
465                continue;
466            };
467            let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap());
468            if sig != SIG_NTH {
469                warn!("Received bad NTH sig.");
470                continue;
471            }
472            let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize;
473
474            // Process NTB Datagram Pointer (NDP)
475            let Some(ndp) = ntb.get(ndp_idx..ndp_idx + 12) else {
476                warn!("NTH has an NDP pointer out of range.");
477                continue;
478            };
479            let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap());
480            if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS {
481                warn!("Received bad NDP sig.");
482                continue;
483            }
484            let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize;
485            let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize;
486
487            if datagram_index == 0 || datagram_len == 0 {
488                // empty, ignore. This is allowed by the spec, so don't warn.
489                continue;
490            }
491
492            // Process actual datagram, finally.
493            let Some(datagram) = ntb.get(datagram_index..datagram_index + datagram_len) else {
494                warn!("NDP has a datagram pointer out of range.");
495                continue;
496            };
497            buf[..datagram_len].copy_from_slice(datagram);
498
499            return Ok(datagram_len);
500        }
501    }
502
503    /// Waits for the USB host to enable this interface
504    pub async fn wait_connection(&mut self) -> Result<(), EndpointError> {
505        loop {
506            self.read_ep.wait_enabled().await;
507            self.comm_ep.wait_enabled().await;
508
509            let buf = [
510                0xA1, //bmRequestType
511                0x00, //bNotificationType = NETWORK_CONNECTION
512                0x01, // wValue = connected
513                0x00,
514                self.data_if.into(), // wIndex = interface
515                0x00,
516                0x00, // wLength
517                0x00,
518            ];
519            match self.comm_ep.write(&buf).await {
520                Ok(()) => break,                   // Done!
521                Err(EndpointError::Disabled) => {} // Got disabled again, wait again.
522                Err(e) => return Err(e),
523            }
524        }
525
526        Ok(())
527    }
528}