usb/cdc-ncm: add embassy-net Device implementation.
This commit is contained in:
449
embassy-usb/src/class/cdc_ncm/embassy_net.rs
Normal file
449
embassy-usb/src/class/cdc_ncm/embassy_net.rs
Normal file
@ -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<MTU>; N_RX],
|
||||
tx: [PacketBuf<MTU>; N_TX],
|
||||
inner: MaybeUninit<StateInner<'d, MTU>>,
|
||||
}
|
||||
|
||||
impl<'d, const MTU: usize, const N_RX: usize, const N_TX: usize> State<'d, MTU, N_RX, N_TX> {
|
||||
const NEW_PACKET: PacketBuf<MTU> = 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<MTU>>,
|
||||
tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf<MTU>>,
|
||||
link_state: Mutex<NoopRawMutex, RefCell<LinkStateState>>,
|
||||
}
|
||||
|
||||
/// 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<MTU>>,
|
||||
rx_usb: Receiver<'d, D>,
|
||||
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
|
||||
link_state: &'d Mutex<NoopRawMutex, RefCell<LinkStateState>>,
|
||||
}
|
||||
|
||||
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<const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||
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<const MTU: usize> {
|
||||
len: usize,
|
||||
buf: [u8; MTU],
|
||||
}
|
||||
|
||||
impl<const MTU: usize> PacketBuf<MTU> {
|
||||
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<MTU>>,
|
||||
tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf<MTU>>,
|
||||
link_state: &'d Mutex<NoopRawMutex, RefCell<LinkStateState>>,
|
||||
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<Self::TxToken<'_>> {
|
||||
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<MTU>>,
|
||||
}
|
||||
|
||||
impl<'a, const MTU: usize> embassy_net::device::RxToken for RxToken<'a, MTU> {
|
||||
fn consume<R, F>(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<MTU>>,
|
||||
}
|
||||
|
||||
impl<'a, const MTU: usize> embassy_net::device::TxToken for TxToken<'a, MTU> {
|
||||
fn consume<R, F>(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<M, RefCell<State>>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
496
embassy-usb/src/class/cdc_ncm/mod.rs
Normal file
496
embassy-usb/src/class/cdc_ncm/mod.rs
Normal file
@ -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<T>(buf: &mut [u8], data: T) -> &[u8] {
|
||||
let len = size_of::<T>();
|
||||
unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) }
|
||||
&buf[..len]
|
||||
}
|
||||
|
||||
pub struct State<'a> {
|
||||
comm_control: MaybeUninit<CommControl<'a>>,
|
||||
data_control: MaybeUninit<DataControl>,
|
||||
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::<NtbParameters>() 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<usize, EndpointError> {
|
||||
// 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(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user