diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs index 2d9b613e..fd2f6707 100644 --- a/embassy-stm32/src/usb_otg/usb.rs +++ b/embassy-stm32/src/usb_otg/usb.rs @@ -791,6 +791,9 @@ impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { w.set_usbaep(enabled); }) }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_out_wakers[ep_addr.index()].wake(); } Direction::In => { // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW @@ -807,6 +810,9 @@ impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { w.set_usbaep(enabled); }) }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_in_wakers[ep_addr.index()].wake(); } } } @@ -1031,7 +1037,21 @@ impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { &self.info } - async fn wait_enabled(&mut self) {} + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_in_wakers[ep_index].register(cx.waker()); + + // SAFETY: atomic read without side effects + if unsafe { T::regs().diepctl(ep_index).read().usbaep() } { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } } impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { @@ -1039,7 +1059,21 @@ impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { &self.info } - async fn wait_enabled(&mut self) {} + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_out_wakers[ep_index].register(cx.waker()); + + // SAFETY: atomic read without side effects + if unsafe { T::regs().doepctl(ep_index).read().usbaep() } { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } } impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { @@ -1092,6 +1126,8 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + trace!("write ep={:?} data={:?}", self.info.addr, buf); + if buf.len() > self.info.max_packet_size as usize { return Err(EndpointError::BufferOverflow); } @@ -1100,18 +1136,21 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { let index = self.info.addr.index(); let state = T::state(); - // Wait for previous transfer to complete + // Wait for previous transfer to complete and check if endpoint is disabled poll_fn(|cx| { state.ep_in_wakers[index].register(cx.waker()); // SAFETY: atomic read with no side effects - if unsafe { r.diepctl(index).read().epena() } { - Poll::Pending + let diepctl = unsafe { r.diepctl(index).read() }; + if !diepctl.usbaep() { + Poll::Ready(Err(EndpointError::Disabled)) + } else if !diepctl.epena() { + Poll::Ready(Ok(())) } else { - Poll::Ready(()) + Poll::Pending } }) - .await; + .await?; if buf.len() > 0 { poll_fn(|cx| { @@ -1167,7 +1206,7 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) }; } - trace!("WRITE OK"); + trace!("write done ep={:?}", self.info.addr); Ok(()) } @@ -1205,8 +1244,8 @@ impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { // Clear NAK to indicate we are ready to receive more data T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| { w.set_cnak(true); - }) - }; + }); + } trace!("SETUP received: {:?}", data); Poll::Ready(data) diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 252d6085..e2b17bfc 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -4,13 +4,13 @@ name = "embassy-stm32f4-examples" version = "0.1.0" license = "MIT OR Apache-2.0" - [dependencies] embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"], optional = true } defmt = "0.3" defmt-rtt = "0.4" @@ -27,5 +27,9 @@ embedded-storage = "0.3.0" micromath = "2.0.0" static_cell = "1.0" +[[bin]] +name = "usb_ethernet" +required-features = ["embassy-net"] + [profile.release] debug = 2 diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs new file mode 100644 index 00000000..cf2885ae --- /dev/null +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -0,0 +1,169 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::Driver; +use embassy_stm32::{interrupt, Config}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type UsbDriver = Driver<'static, embassy_stm32::peripherals::USB_OTG_FS>; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, UsbDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(48)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let irq = interrupt::take!(OTG_FS); + let ep_out_buffer = &mut singleton!([0; 256])[..]; + let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, ep_out_buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::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; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], + None, + ); + + // 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, singleton!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::ConfigStrategy::Dhcp; + //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let mut rng = Rng::new(p.RNG); + let mut seed = [0; 8]; + unwrap!(rng.async_fill_bytes(&mut seed).await); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + config, + singleton!(StackResources::<1, 2, 8>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // 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(stack, &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; + } + }; + } + } +}