From 50e1d257bd192a864a11b6e1edc86298eda7b943 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 23 Apr 2022 05:14:44 +0200 Subject: [PATCH 1/3] net: change listen() to accept(), which waits until the connection is established. --- embassy-net/src/tcp_socket.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/embassy-net/src/tcp_socket.rs b/embassy-net/src/tcp_socket.rs index 4836f807..5637505d 100644 --- a/embassy-net/src/tcp_socket.rs +++ b/embassy-net/src/tcp_socket.rs @@ -58,7 +58,7 @@ impl<'a> TcpSocket<'a> { .await } - pub async fn listen(&mut self, local_endpoint: T) -> Result<()> + pub async fn accept(&mut self, local_endpoint: T) -> Result<()> where T: Into, { @@ -66,9 +66,7 @@ impl<'a> TcpSocket<'a> { futures::future::poll_fn(|cx| { self.with(|s, _| match s.state() { - TcpState::Closed | TcpState::TimeWait => Poll::Ready(Err(Error::Unaddressable)), - TcpState::Listen => Poll::Ready(Ok(())), - TcpState::SynSent | TcpState::SynReceived => { + TcpState::Listen | TcpState::SynSent | TcpState::SynReceived => { s.register_send_waker(cx.waker()); Poll::Pending } From d57fd87ba77f923953e5b4d401c8cf39c45b0842 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 23 Apr 2022 05:24:38 +0200 Subject: [PATCH 2/3] Add embassy-usb-ncm. Implements USBB CDC NCM (Ethernet over USB) --- embassy-usb-ncm/Cargo.toml | 19 ++ embassy-usb-ncm/src/fmt.rs | 225 ++++++++++++ embassy-usb-ncm/src/lib.rs | 489 +++++++++++++++++++++++++++ examples/nrf/Cargo.toml | 4 +- examples/nrf/src/bin/usb_ethernet.rs | 277 +++++++++++++++ 5 files changed, 1013 insertions(+), 1 deletion(-) create mode 100644 embassy-usb-ncm/Cargo.toml create mode 100644 embassy-usb-ncm/src/fmt.rs create mode 100644 embassy-usb-ncm/src/lib.rs create mode 100644 examples/nrf/src/bin/usb_ethernet.rs diff --git a/embassy-usb-ncm/Cargo.toml b/embassy-usb-ncm/Cargo.toml new file mode 100644 index 00000000..aaef01d6 --- /dev/null +++ b/embassy-usb-ncm/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "embassy-usb-ncm" +version = "0.1.0" +edition = "2021" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-ncm-v$VERSION/embassy-usb-ncm/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/master/embassy-usb-ncm/src/" +features = ["defmt"] +flavors = [ + { name = "default", target = "thumbv7em-none-eabihf" }, +] + +[dependencies] +embassy = { version = "0.1.0", path = "../embassy" } +embassy-usb = { version = "0.1.0", path = "../embassy-usb" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-ncm/src/fmt.rs b/embassy-usb-ncm/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-usb-ncm/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-usb-ncm/src/lib.rs b/embassy-usb-ncm/src/lib.rs new file mode 100644 index 00000000..3a5abee8 --- /dev/null +++ b/embassy-usb-ncm/src/lib.rs @@ -0,0 +1,489 @@ +#![no_std] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +use core::cell::Cell; +use core::intrinsics::copy_nonoverlapping; +use core::mem::{size_of, MaybeUninit}; +use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; +use embassy_usb::driver::{Endpoint, EndpointError, EndpointIn, EndpointOut}; +use embassy_usb::{driver::Driver, types::*, 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: Cell<[u8; 6]>, +} + +impl Default for ControlShared { + fn default() -> Self { + ControlShared { + mac_addr: Cell::new([0; 6]), + } + } +} + +struct CommControl<'a> { + mac_addr_string: StringIndex, + shared: &'a ControlShared, +} + +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<'a>( + &'a mut self, + index: StringIndex, + _lang_id: u16, + buf: &'a mut [u8], + ) -> Option<&'a str> { + if index == self.mac_addr_string { + let mac_addr = self.shared.mac_addr.get(); + for i in 0..12 { + let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF; + buf[i] = match n { + 0x0..=0x9 => b'0' + n, + 0xA..=0xF => b'A' + n - 0xA, + _ => unreachable!(), + } + } + + Some(unsafe { core::str::from_utf8_unchecked(&buf[..12]) }) + } 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 { + let control_shared = &state.shared; + control_shared.mac_addr.set(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: &control_shared, + })); + 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: control_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/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index e944c171..4258544f 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -6,14 +6,16 @@ version = "0.1.0" [features] default = ["nightly"] -nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid"] +nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid", "embassy-usb-ncm"] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true } +embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"], optional = true } defmt = "0.3" defmt-rtt = "0.3" diff --git a/examples/nrf/src/bin/usb_ethernet.rs b/examples/nrf/src/bin/usb_ethernet.rs new file mode 100644 index 00000000..70460d23 --- /dev/null +++ b/examples/nrf/src/bin/usb_ethernet.rs @@ -0,0 +1,277 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +use core::mem; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Waker; +use defmt::*; +use embassy::blocking_mutex::raw::ThreadModeRawMutex; +use embassy::channel::Channel; +use embassy::executor::Spawner; +use embassy::io::{AsyncBufReadExt, AsyncWriteExt}; +use embassy::util::Forever; +use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, TcpSocket}; +use embassy_nrf::pac; +use embassy_nrf::usb::Driver; +use embassy_nrf::Peripherals; +use embassy_nrf::{interrupt, peripherals}; +use embassy_usb::{Builder, Config, UsbDevice}; +use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; + +use defmt_rtt as _; // global logger +use panic_probe as _; + +type MyDriver = Driver<'static, peripherals::USBD>; + +#[embassy::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { + device.run().await +} + +#[embassy::task] +async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { + loop { + warn!("WAITING for connection"); + LINK_UP.store(false, Ordering::Relaxed); + + class.wait_connection().await.unwrap(); + + warn!("Connected"); + LINK_UP.store(true, Ordering::Relaxed); + + loop { + let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); + let n = match class.read_packet(&mut p[..]).await { + Ok(n) => n, + Err(e) => { + warn!("error reading packet: {:?}", e); + break; + } + }; + + let buf = p.slice(0..n); + if RX_CHANNEL.try_send(buf).is_err() { + warn!("Failed pushing rx'd packet to channel."); + } + } + } +} + +#[embassy::task] +async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { + loop { + let pkt = TX_CHANNEL.recv().await; + if let Err(e) = class.write_packet(&pkt[..]).await { + warn!("Failed to TX packet: {:?}", e); + } + } +} + +#[embassy::task] +async fn net_task() -> ! { + embassy_net::run().await +} + +#[embassy::main] +async fn main(spawner: Spawner, p: Peripherals) { + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + let power: pac::POWER = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + info!("Waiting for vbus..."); + while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} + info!("vbus OK"); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let driver = Driver::new(p.USBD, irq); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + struct Resources { + device_descriptor: [u8; 256], + config_descriptor: [u8; 256], + bos_descriptor: [u8; 256], + control_buf: [u8; 128], + serial_state: State<'static>, + } + static RESOURCES: Forever = Forever::new(); + let res = RESOURCES.put(Resources { + device_descriptor: [0; 256], + config_descriptor: [0; 256], + bos_descriptor: [0; 256], + control_buf: [0; 128], + serial_state: State::new(), + }); + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut res.device_descriptor, + &mut res.config_descriptor, + &mut res.bos_descriptor, + &mut res.control_buf, + None, + ); + + // WARNINGS for Android ethernet tethering: + // - 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 + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (tx, rx) = class.split(); + unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); + unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + + // Init embassy-net + struct NetResources { + resources: embassy_net::StackResources<1, 2, 8>, + configurator: embassy_net::DhcpConfigurator, + //configurator: StaticConfigurator, + device: Device, + } + static NET_RESOURCES: Forever = Forever::new(); + let res = NET_RESOURCES.put(NetResources { + resources: embassy_net::StackResources::new(), + configurator: embassy_net::DhcpConfigurator::new(), + //configurator: embassy_net::StaticConfigurator::new(embassy_net::Config { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 1), 24), + // dns_servers: Default::default(), + // gateway: None, + //}), + device: Device { + mac_addr: our_mac_addr, + }, + }); + embassy_net::init(&mut res.device, &mut res.configurator, &mut res.resources); + unwrap!(spawner.spawn(net_task())); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(&mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} + +static TX_CHANNEL: Channel = Channel::new(); +static RX_CHANNEL: Channel = Channel::new(); +static LINK_UP: AtomicBool = AtomicBool::new(false); + +struct Device { + mac_addr: [u8; 6], +} + +impl embassy_net::Device for Device { + fn register_waker(&mut self, waker: &Waker) { + // loopy loopy wakey wakey + waker.wake_by_ref() + } + + fn link_state(&mut self) -> embassy_net::LinkState { + match LINK_UP.load(Ordering::Relaxed) { + true => embassy_net::LinkState::Up, + false => embassy_net::LinkState::Down, + } + } + + fn capabilities(&mut self) -> embassy_net::DeviceCapabilities { + let mut caps = embassy_net::DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header + caps.medium = embassy_net::Medium::Ethernet; + caps + } + + fn is_transmit_ready(&mut self) -> bool { + true + } + + fn transmit(&mut self, pkt: PacketBuf) { + if TX_CHANNEL.try_send(pkt).is_err() { + warn!("TX failed") + } + } + + fn receive<'a>(&mut self) -> Option { + RX_CHANNEL.try_recv().ok() + } + + fn ethernet_address(&mut self) -> [u8; 6] { + self.mac_addr + } +} + +#[no_mangle] +fn _embassy_rand(buf: &mut [u8]) { + // TODO + buf.fill(0x42) +} From d409026b95c24c1582e17d3f40daea2b75cb9304 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 24 Apr 2022 22:46:45 +0200 Subject: [PATCH 3/3] examples/nrf: add product strings to all usb examples. --- examples/nrf/src/bin/usb_hid_keyboard.rs | 4 ++-- examples/nrf/src/bin/usb_hid_mouse.rs | 5 +++-- examples/nrf/src/bin/usb_serial.rs | 7 ++++++- examples/nrf/src/bin/usb_serial_multitask.rs | 7 ++++++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 9fa7ab33..3852dd8d 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -59,8 +59,8 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); - config.manufacturer = Some("Tactile Engineering"); - config.product = Some("Testy"); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); config.serial_number = Some("12345678"); config.max_power = 100; config.max_packet_size_0 = 64; diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index 92aeffda..e70dc51a 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -39,10 +39,11 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); - config.manufacturer = Some("Tactile Engineering"); - config.product = Some("Testy"); + config.manufacturer = Some("Embassy"); + config.product = Some("HID mouse example"); config.serial_number = Some("12345678"); config.max_power = 100; + config.max_packet_size_0 = 64; // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 6081ee91..bc41c2ac 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -36,7 +36,12 @@ async fn main(_spawner: Spawner, p: Peripherals) { let driver = Driver::new(p.USBD, irq); // Create embassy-usb Config - let config = Config::new(0xc0de, 0xcafe); + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs index d4b3000e..31e0af48 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf/src/bin/usb_serial_multitask.rs @@ -53,7 +53,12 @@ async fn main(spawner: Spawner, p: Peripherals) { let driver = Driver::new(p.USBD, irq); // Create embassy-usb Config - let config = Config::new(0xc0de, 0xcafe); + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; struct Resources { device_descriptor: [u8; 256],