aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb/src/class/cdc_ncm/mod.rs
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2022-12-07 16:03:03 +0100
committerDario Nieuwenhuis <[email protected]>2022-12-13 16:43:25 +0100
commite9219405ca04e23b6543fb841fd97df54cf72f94 (patch)
tree32d9129286949fdb887898adad1076ab352e95e9 /embassy-usb/src/class/cdc_ncm/mod.rs
parentaaaf5f23a8a3a0df1ad2186802e2afc061a74b72 (diff)
usb/cdc-ncm: add embassy-net Device implementation.
Diffstat (limited to 'embassy-usb/src/class/cdc_ncm/mod.rs')
-rw-r--r--embassy-usb/src/class/cdc_ncm/mod.rs496
1 files changed, 496 insertions, 0 deletions
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 @@
1/// CDC-NCM, aka Ethernet over USB.
2///
3/// # Compatibility
4///
5/// Windows: NOT supported in Windows 10. Supported 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
16use core::intrinsics::copy_nonoverlapping;
17use core::mem::{size_of, MaybeUninit};
18
19use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
20use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
21use crate::types::*;
22use crate::Builder;
23
24#[cfg(feature = "embassy-net")]
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 = 0x484d434e;
66const SIG_NDP_NO_FCS: u32 = 0x304d434e;
67const SIG_NDP_WITH_FCS: u32 = 0x314d434e;
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(&data as *const _ as *const u8, buf.as_mut_ptr(), len) }
115 &buf[..len]
116}
117
118pub struct State<'a> {
119 comm_control: MaybeUninit<CommControl<'a>>,
120 data_control: MaybeUninit<DataControl>,
121 shared: ControlShared,
122}
123
124impl<'a> State<'a> {
125 pub fn new() -> Self {
126 Self {
127 comm_control: MaybeUninit::uninit(),
128 data_control: MaybeUninit::uninit(),
129 shared: Default::default(),
130 }
131 }
132}
133
134/// Shared data between Control and CdcAcmClass
135struct ControlShared {
136 mac_addr: [u8; 6],
137}
138
139impl Default for ControlShared {
140 fn default() -> Self {
141 ControlShared { mac_addr: [0; 6] }
142 }
143}
144
145struct CommControl<'a> {
146 mac_addr_string: StringIndex,
147 shared: &'a ControlShared,
148 mac_addr_str: [u8; 12],
149}
150
151impl<'d> ControlHandler for CommControl<'d> {
152 fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse {
153 match req.request {
154 REQ_SEND_ENCAPSULATED_COMMAND => {
155 // We don't actually support encapsulated commands but pretend we do for standards
156 // compatibility.
157 OutResponse::Accepted
158 }
159 REQ_SET_NTB_INPUT_SIZE => {
160 // TODO
161 OutResponse::Accepted
162 }
163 _ => OutResponse::Rejected,
164 }
165 }
166
167 fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
168 match req.request {
169 REQ_GET_NTB_PARAMETERS => {
170 let res = NtbParameters {
171 length: size_of::<NtbParameters>() as _,
172 formats_supported: 1, // only 16bit,
173 in_params: NtbParametersDir {
174 max_size: NTB_MAX_SIZE as _,
175 divisor: 4,
176 payload_remainder: 0,
177 out_alignment: 4,
178 max_datagram_count: 0, // not used
179 },
180 out_params: NtbParametersDir {
181 max_size: NTB_MAX_SIZE as _,
182 divisor: 4,
183 payload_remainder: 0,
184 out_alignment: 4,
185 max_datagram_count: 1, // We only decode 1 packet per NTB
186 },
187 };
188 InResponse::Accepted(byteify(buf, res))
189 }
190 _ => InResponse::Rejected,
191 }
192 }
193
194 fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> {
195 if index == self.mac_addr_string {
196 let mac_addr = self.shared.mac_addr;
197 let s = &mut self.mac_addr_str;
198 for i in 0..12 {
199 let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF;
200 s[i] = match n {
201 0x0..=0x9 => b'0' + n,
202 0xA..=0xF => b'A' + n - 0xA,
203 _ => unreachable!(),
204 }
205 }
206
207 Some(unsafe { core::str::from_utf8_unchecked(s) })
208 } else {
209 warn!("unknown string index requested");
210 None
211 }
212 }
213}
214
215struct DataControl {}
216
217impl ControlHandler for DataControl {
218 fn set_alternate_setting(&mut self, alternate_setting: u8) {
219 match alternate_setting {
220 ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"),
221 ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"),
222 _ => unreachable!(),
223 }
224 }
225}
226
227pub struct CdcNcmClass<'d, D: Driver<'d>> {
228 _comm_if: InterfaceNumber,
229 comm_ep: D::EndpointIn,
230
231 data_if: InterfaceNumber,
232 read_ep: D::EndpointOut,
233 write_ep: D::EndpointIn,
234
235 _control: &'d ControlShared,
236}
237
238impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
239 pub fn new(
240 builder: &mut Builder<'d, D>,
241 state: &'d mut State<'d>,
242 mac_address: [u8; 6],
243 max_packet_size: u16,
244 ) -> Self {
245 state.shared.mac_addr = mac_address;
246
247 let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE);
248
249 // Control interface
250 let mut iface = func.interface();
251 let mac_addr_string = iface.string();
252 iface.handler(state.comm_control.write(CommControl {
253 mac_addr_string,
254 shared: &state.shared,
255 mac_addr_str: [0; 12],
256 }));
257 let comm_if = iface.interface_number();
258 let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE);
259
260 alt.descriptor(
261 CS_INTERFACE,
262 &[
263 CDC_TYPE_HEADER, // bDescriptorSubtype
264 0x10,
265 0x01, // bcdCDC (1.10)
266 ],
267 );
268 alt.descriptor(
269 CS_INTERFACE,
270 &[
271 CDC_TYPE_UNION, // bDescriptorSubtype
272 comm_if.into(), // bControlInterface
273 u8::from(comm_if) + 1, // bSubordinateInterface
274 ],
275 );
276 alt.descriptor(
277 CS_INTERFACE,
278 &[
279 CDC_TYPE_ETHERNET, // bDescriptorSubtype
280 mac_addr_string.into(), // iMACAddress
281 0, // bmEthernetStatistics
282 0, // |
283 0, // |
284 0, // |
285 0xea, // wMaxSegmentSize = 1514
286 0x05, // |
287 0, // wNumberMCFilters
288 0, // |
289 0, // bNumberPowerFilters
290 ],
291 );
292 alt.descriptor(
293 CS_INTERFACE,
294 &[
295 CDC_TYPE_NCM, // bDescriptorSubtype
296 0x00, // bcdNCMVersion
297 0x01, // |
298 0, // bmNetworkCapabilities
299 ],
300 );
301
302 let comm_ep = alt.endpoint_interrupt_in(8, 255);
303
304 // Data interface
305 let mut iface = func.interface();
306 iface.handler(state.data_control.write(DataControl {}));
307 let data_if = iface.interface_number();
308 let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB);
309 let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB);
310 let read_ep = alt.endpoint_bulk_out(max_packet_size);
311 let write_ep = alt.endpoint_bulk_in(max_packet_size);
312
313 CdcNcmClass {
314 _comm_if: comm_if,
315 comm_ep,
316 data_if,
317 read_ep,
318 write_ep,
319 _control: &state.shared,
320 }
321 }
322
323 pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
324 (
325 Sender {
326 write_ep: self.write_ep,
327 seq: 0,
328 },
329 Receiver {
330 data_if: self.data_if,
331 comm_ep: self.comm_ep,
332 read_ep: self.read_ep,
333 },
334 )
335 }
336}
337
338pub struct Sender<'d, D: Driver<'d>> {
339 write_ep: D::EndpointIn,
340 seq: u16,
341}
342
343impl<'d, D: Driver<'d>> Sender<'d, D> {
344 pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
345 let seq = self.seq;
346 self.seq = self.seq.wrapping_add(1);
347
348 const MAX_PACKET_SIZE: usize = 64; // TODO unhardcode
349 const OUT_HEADER_LEN: usize = 28;
350
351 let header = NtbOutHeader {
352 nth_sig: SIG_NTH,
353 nth_len: 0x0c,
354 nth_seq: seq,
355 nth_total_len: (data.len() + OUT_HEADER_LEN) as u16,
356 nth_first_index: 0x0c,
357
358 ndp_sig: SIG_NDP_NO_FCS,
359 ndp_len: 0x10,
360 ndp_next_index: 0x00,
361 ndp_datagram_index: OUT_HEADER_LEN as u16,
362 ndp_datagram_len: data.len() as u16,
363 ndp_term1: 0x00,
364 ndp_term2: 0x00,
365 };
366
367 // Build first packet on a buffer, send next packets straight from `data`.
368 let mut buf = [0; MAX_PACKET_SIZE];
369 let n = byteify(&mut buf, header);
370 assert_eq!(n.len(), OUT_HEADER_LEN);
371
372 if OUT_HEADER_LEN + data.len() < MAX_PACKET_SIZE {
373 // First packet is not full, just send it.
374 // No need to send ZLP because it's short for sure.
375 buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data);
376 self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?;
377 } else {
378 let (d1, d2) = data.split_at(MAX_PACKET_SIZE - OUT_HEADER_LEN);
379
380 buf[OUT_HEADER_LEN..].copy_from_slice(d1);
381 self.write_ep.write(&buf).await?;
382
383 for chunk in d2.chunks(MAX_PACKET_SIZE) {
384 self.write_ep.write(&chunk).await?;
385 }
386
387 // Send ZLP if needed.
388 if d2.len() % MAX_PACKET_SIZE == 0 {
389 self.write_ep.write(&[]).await?;
390 }
391 }
392
393 Ok(())
394 }
395}
396
397pub struct Receiver<'d, D: Driver<'d>> {
398 data_if: InterfaceNumber,
399 comm_ep: D::EndpointIn,
400 read_ep: D::EndpointOut,
401}
402
403impl<'d, D: Driver<'d>> Receiver<'d, D> {
404 /// Reads a single packet from the OUT endpoint.
405 pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
406 // Retry loop
407 loop {
408 // read NTB
409 let mut ntb = [0u8; NTB_MAX_SIZE];
410 let mut pos = 0;
411 loop {
412 let n = self.read_ep.read(&mut ntb[pos..]).await?;
413 pos += n;
414 if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE {
415 break;
416 }
417 }
418
419 let ntb = &ntb[..pos];
420
421 // Process NTB header (NTH)
422 let nth = match ntb.get(..12) {
423 Some(x) => x,
424 None => {
425 warn!("Received too short NTB");
426 continue;
427 }
428 };
429 let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap());
430 if sig != SIG_NTH {
431 warn!("Received bad NTH sig.");
432 continue;
433 }
434 let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize;
435
436 // Process NTB Datagram Pointer (NDP)
437 let ndp = match ntb.get(ndp_idx..ndp_idx + 12) {
438 Some(x) => x,
439 None => {
440 warn!("NTH has an NDP pointer out of range.");
441 continue;
442 }
443 };
444 let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap());
445 if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS {
446 warn!("Received bad NDP sig.");
447 continue;
448 }
449 let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize;
450 let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize;
451
452 if datagram_index == 0 || datagram_len == 0 {
453 // empty, ignore. This is allowed by the spec, so don't warn.
454 continue;
455 }
456
457 // Process actual datagram, finally.
458 let datagram = match ntb.get(datagram_index..datagram_index + datagram_len) {
459 Some(x) => x,
460 None => {
461 warn!("NDP has a datagram pointer out of range.");
462 continue;
463 }
464 };
465 buf[..datagram_len].copy_from_slice(datagram);
466
467 return Ok(datagram_len);
468 }
469 }
470
471 /// Waits for the USB host to enable this interface
472 pub async fn wait_connection(&mut self) -> Result<(), EndpointError> {
473 loop {
474 self.read_ep.wait_enabled().await;
475 self.comm_ep.wait_enabled().await;
476
477 let buf = [
478 0xA1, //bmRequestType
479 0x00, //bNotificationType = NETWORK_CONNECTION
480 0x01, // wValue = connected
481 0x00,
482 self.data_if.into(), // wIndex = interface
483 0x00,
484 0x00, // wLength
485 0x00,
486 ];
487 match self.comm_ep.write(&buf).await {
488 Ok(()) => break, // Done!
489 Err(EndpointError::Disabled) => {} // Got disabled again, wait again.
490 Err(e) => return Err(e),
491 }
492 }
493
494 Ok(())
495 }
496}