diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2023-01-18 11:30:44 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-01-18 11:30:44 +0000 |
| commit | f0ae1f9133463459a7ad06aa15fb899c2f4185e2 (patch) | |
| tree | 61f2ad783a8338754ca2fb6bb68af7f0cb6bd089 | |
| parent | db8e9efe731c8f37927f9d1debac4628c9894204 (diff) | |
| parent | b1203bf036454bf28903fc079086e3cc93a18446 (diff) | |
Merge #1159
1159: stm32 usb otg bug fixes r=Dirbaio a=chemicstry
This fixes a couple of usb otg bugs that surfaced with `usb_ethernet` example from nrf:
- Properly implemented `Endpoint::wait_enabled()`
- Return `EndpointError::Disabled` when neccessary in `Endpoint::write()`
Co-authored-by: chemicstry <[email protected]>
| -rw-r--r-- | embassy-stm32/src/usb_otg/usb.rs | 59 | ||||
| -rw-r--r-- | examples/stm32f4/Cargo.toml | 6 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/usb_ethernet.rs | 169 |
3 files changed, 223 insertions, 11 deletions
diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs index 2d9b613ea..fd2f67073 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> { | |||
| 791 | w.set_usbaep(enabled); | 791 | w.set_usbaep(enabled); |
| 792 | }) | 792 | }) |
| 793 | }); | 793 | }); |
| 794 | |||
| 795 | // Wake `Endpoint::wait_enabled()` | ||
| 796 | T::state().ep_out_wakers[ep_addr.index()].wake(); | ||
| 794 | } | 797 | } |
| 795 | Direction::In => { | 798 | Direction::In => { |
| 796 | // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW | 799 | // 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> { | |||
| 807 | w.set_usbaep(enabled); | 810 | w.set_usbaep(enabled); |
| 808 | }) | 811 | }) |
| 809 | }); | 812 | }); |
| 813 | |||
| 814 | // Wake `Endpoint::wait_enabled()` | ||
| 815 | T::state().ep_in_wakers[ep_addr.index()].wake(); | ||
| 810 | } | 816 | } |
| 811 | } | 817 | } |
| 812 | } | 818 | } |
| @@ -1031,7 +1037,21 @@ impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { | |||
| 1031 | &self.info | 1037 | &self.info |
| 1032 | } | 1038 | } |
| 1033 | 1039 | ||
| 1034 | async fn wait_enabled(&mut self) {} | 1040 | async fn wait_enabled(&mut self) { |
| 1041 | poll_fn(|cx| { | ||
| 1042 | let ep_index = self.info.addr.index(); | ||
| 1043 | |||
| 1044 | T::state().ep_in_wakers[ep_index].register(cx.waker()); | ||
| 1045 | |||
| 1046 | // SAFETY: atomic read without side effects | ||
| 1047 | if unsafe { T::regs().diepctl(ep_index).read().usbaep() } { | ||
| 1048 | Poll::Ready(()) | ||
| 1049 | } else { | ||
| 1050 | Poll::Pending | ||
| 1051 | } | ||
| 1052 | }) | ||
| 1053 | .await | ||
| 1054 | } | ||
| 1035 | } | 1055 | } |
| 1036 | 1056 | ||
| 1037 | impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { | 1057 | 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> { | |||
| 1039 | &self.info | 1059 | &self.info |
| 1040 | } | 1060 | } |
| 1041 | 1061 | ||
| 1042 | async fn wait_enabled(&mut self) {} | 1062 | async fn wait_enabled(&mut self) { |
| 1063 | poll_fn(|cx| { | ||
| 1064 | let ep_index = self.info.addr.index(); | ||
| 1065 | |||
| 1066 | T::state().ep_out_wakers[ep_index].register(cx.waker()); | ||
| 1067 | |||
| 1068 | // SAFETY: atomic read without side effects | ||
| 1069 | if unsafe { T::regs().doepctl(ep_index).read().usbaep() } { | ||
| 1070 | Poll::Ready(()) | ||
| 1071 | } else { | ||
| 1072 | Poll::Pending | ||
| 1073 | } | ||
| 1074 | }) | ||
| 1075 | .await | ||
| 1076 | } | ||
| 1043 | } | 1077 | } |
| 1044 | 1078 | ||
| 1045 | impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { | 1079 | 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> { | |||
| 1092 | 1126 | ||
| 1093 | impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { | 1127 | impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { |
| 1094 | async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { | 1128 | async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { |
| 1129 | trace!("write ep={:?} data={:?}", self.info.addr, buf); | ||
| 1130 | |||
| 1095 | if buf.len() > self.info.max_packet_size as usize { | 1131 | if buf.len() > self.info.max_packet_size as usize { |
| 1096 | return Err(EndpointError::BufferOverflow); | 1132 | return Err(EndpointError::BufferOverflow); |
| 1097 | } | 1133 | } |
| @@ -1100,18 +1136,21 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { | |||
| 1100 | let index = self.info.addr.index(); | 1136 | let index = self.info.addr.index(); |
| 1101 | let state = T::state(); | 1137 | let state = T::state(); |
| 1102 | 1138 | ||
| 1103 | // Wait for previous transfer to complete | 1139 | // Wait for previous transfer to complete and check if endpoint is disabled |
| 1104 | poll_fn(|cx| { | 1140 | poll_fn(|cx| { |
| 1105 | state.ep_in_wakers[index].register(cx.waker()); | 1141 | state.ep_in_wakers[index].register(cx.waker()); |
| 1106 | 1142 | ||
| 1107 | // SAFETY: atomic read with no side effects | 1143 | // SAFETY: atomic read with no side effects |
| 1108 | if unsafe { r.diepctl(index).read().epena() } { | 1144 | let diepctl = unsafe { r.diepctl(index).read() }; |
| 1109 | Poll::Pending | 1145 | if !diepctl.usbaep() { |
| 1146 | Poll::Ready(Err(EndpointError::Disabled)) | ||
| 1147 | } else if !diepctl.epena() { | ||
| 1148 | Poll::Ready(Ok(())) | ||
| 1110 | } else { | 1149 | } else { |
| 1111 | Poll::Ready(()) | 1150 | Poll::Pending |
| 1112 | } | 1151 | } |
| 1113 | }) | 1152 | }) |
| 1114 | .await; | 1153 | .await?; |
| 1115 | 1154 | ||
| 1116 | if buf.len() > 0 { | 1155 | if buf.len() > 0 { |
| 1117 | poll_fn(|cx| { | 1156 | poll_fn(|cx| { |
| @@ -1167,7 +1206,7 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { | |||
| 1167 | unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) }; | 1206 | unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) }; |
| 1168 | } | 1207 | } |
| 1169 | 1208 | ||
| 1170 | trace!("WRITE OK"); | 1209 | trace!("write done ep={:?}", self.info.addr); |
| 1171 | 1210 | ||
| 1172 | Ok(()) | 1211 | Ok(()) |
| 1173 | } | 1212 | } |
| @@ -1205,8 +1244,8 @@ impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { | |||
| 1205 | // Clear NAK to indicate we are ready to receive more data | 1244 | // Clear NAK to indicate we are ready to receive more data |
| 1206 | T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| { | 1245 | T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| { |
| 1207 | w.set_cnak(true); | 1246 | w.set_cnak(true); |
| 1208 | }) | 1247 | }); |
| 1209 | }; | 1248 | } |
| 1210 | 1249 | ||
| 1211 | trace!("SETUP received: {:?}", data); | 1250 | trace!("SETUP received: {:?}", data); |
| 1212 | Poll::Ready(data) | 1251 | Poll::Ready(data) |
diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 252d60855..e2b17bfcb 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml | |||
| @@ -4,13 +4,13 @@ name = "embassy-stm32f4-examples" | |||
| 4 | version = "0.1.0" | 4 | version = "0.1.0" |
| 5 | license = "MIT OR Apache-2.0" | 5 | license = "MIT OR Apache-2.0" |
| 6 | 6 | ||
| 7 | |||
| 8 | [dependencies] | 7 | [dependencies] |
| 9 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } | 8 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } |
| 10 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } | 9 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } |
| 11 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } | 10 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } |
| 12 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } | 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } |
| 13 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | 12 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } |
| 13 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"], optional = true } | ||
| 14 | 14 | ||
| 15 | defmt = "0.3" | 15 | defmt = "0.3" |
| 16 | defmt-rtt = "0.4" | 16 | defmt-rtt = "0.4" |
| @@ -27,5 +27,9 @@ embedded-storage = "0.3.0" | |||
| 27 | micromath = "2.0.0" | 27 | micromath = "2.0.0" |
| 28 | static_cell = "1.0" | 28 | static_cell = "1.0" |
| 29 | 29 | ||
| 30 | [[bin]] | ||
| 31 | name = "usb_ethernet" | ||
| 32 | required-features = ["embassy-net"] | ||
| 33 | |||
| 30 | [profile.release] | 34 | [profile.release] |
| 31 | debug = 2 | 35 | 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 000000000..cf2885ae5 --- /dev/null +++ b/examples/stm32f4/src/bin/usb_ethernet.rs | |||
| @@ -0,0 +1,169 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_net::tcp::TcpSocket; | ||
| 8 | use embassy_net::{Stack, StackResources}; | ||
| 9 | use embassy_stm32::rng::Rng; | ||
| 10 | use embassy_stm32::time::mhz; | ||
| 11 | use embassy_stm32::usb_otg::Driver; | ||
| 12 | use embassy_stm32::{interrupt, Config}; | ||
| 13 | use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; | ||
| 14 | use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; | ||
| 15 | use embassy_usb::{Builder, UsbDevice}; | ||
| 16 | use embedded_io::asynch::Write; | ||
| 17 | use static_cell::StaticCell; | ||
| 18 | use {defmt_rtt as _, panic_probe as _}; | ||
| 19 | |||
| 20 | type UsbDriver = Driver<'static, embassy_stm32::peripherals::USB_OTG_FS>; | ||
| 21 | |||
| 22 | macro_rules! singleton { | ||
| 23 | ($val:expr) => {{ | ||
| 24 | type T = impl Sized; | ||
| 25 | static STATIC_CELL: StaticCell<T> = StaticCell::new(); | ||
| 26 | let (x,) = STATIC_CELL.init(($val,)); | ||
| 27 | x | ||
| 28 | }}; | ||
| 29 | } | ||
| 30 | |||
| 31 | const MTU: usize = 1514; | ||
| 32 | |||
| 33 | #[embassy_executor::task] | ||
| 34 | async fn usb_task(mut device: UsbDevice<'static, UsbDriver>) -> ! { | ||
| 35 | device.run().await | ||
| 36 | } | ||
| 37 | |||
| 38 | #[embassy_executor::task] | ||
| 39 | async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { | ||
| 40 | class.run().await | ||
| 41 | } | ||
| 42 | |||
| 43 | #[embassy_executor::task] | ||
| 44 | async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! { | ||
| 45 | stack.run().await | ||
| 46 | } | ||
| 47 | |||
| 48 | #[embassy_executor::main] | ||
| 49 | async fn main(spawner: Spawner) { | ||
| 50 | info!("Hello World!"); | ||
| 51 | |||
| 52 | let mut config = Config::default(); | ||
| 53 | config.rcc.pll48 = true; | ||
| 54 | config.rcc.sys_ck = Some(mhz(48)); | ||
| 55 | |||
| 56 | let p = embassy_stm32::init(config); | ||
| 57 | |||
| 58 | // Create the driver, from the HAL. | ||
| 59 | let irq = interrupt::take!(OTG_FS); | ||
| 60 | let ep_out_buffer = &mut singleton!([0; 256])[..]; | ||
| 61 | let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, ep_out_buffer); | ||
| 62 | |||
| 63 | // Create embassy-usb Config | ||
| 64 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 65 | config.manufacturer = Some("Embassy"); | ||
| 66 | config.product = Some("USB-Ethernet example"); | ||
| 67 | config.serial_number = Some("12345678"); | ||
| 68 | config.max_power = 100; | ||
| 69 | config.max_packet_size_0 = 64; | ||
| 70 | |||
| 71 | // Required for Windows support. | ||
| 72 | config.composite_with_iads = true; | ||
| 73 | config.device_class = 0xEF; | ||
| 74 | config.device_sub_class = 0x02; | ||
| 75 | config.device_protocol = 0x01; | ||
| 76 | |||
| 77 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 78 | let mut builder = Builder::new( | ||
| 79 | driver, | ||
| 80 | config, | ||
| 81 | &mut singleton!([0; 256])[..], | ||
| 82 | &mut singleton!([0; 256])[..], | ||
| 83 | &mut singleton!([0; 256])[..], | ||
| 84 | &mut singleton!([0; 128])[..], | ||
| 85 | None, | ||
| 86 | ); | ||
| 87 | |||
| 88 | // Our MAC addr. | ||
| 89 | let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; | ||
| 90 | // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. | ||
| 91 | let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; | ||
| 92 | |||
| 93 | // Create classes on the builder. | ||
| 94 | let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); | ||
| 95 | |||
| 96 | // Build the builder. | ||
| 97 | let usb = builder.build(); | ||
| 98 | |||
| 99 | unwrap!(spawner.spawn(usb_task(usb))); | ||
| 100 | |||
| 101 | let (runner, device) = class.into_embassy_net_device::<MTU, 4, 4>(singleton!(NetState::new()), our_mac_addr); | ||
| 102 | unwrap!(spawner.spawn(usb_ncm_task(runner))); | ||
| 103 | |||
| 104 | let config = embassy_net::ConfigStrategy::Dhcp; | ||
| 105 | //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { | ||
| 106 | // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), | ||
| 107 | // dns_servers: Vec::new(), | ||
| 108 | // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), | ||
| 109 | //}); | ||
| 110 | |||
| 111 | // Generate random seed | ||
| 112 | let mut rng = Rng::new(p.RNG); | ||
| 113 | let mut seed = [0; 8]; | ||
| 114 | unwrap!(rng.async_fill_bytes(&mut seed).await); | ||
| 115 | let seed = u64::from_le_bytes(seed); | ||
| 116 | |||
| 117 | // Init network stack | ||
| 118 | let stack = &*singleton!(Stack::new( | ||
| 119 | device, | ||
| 120 | config, | ||
| 121 | singleton!(StackResources::<1, 2, 8>::new()), | ||
| 122 | seed | ||
| 123 | )); | ||
| 124 | |||
| 125 | unwrap!(spawner.spawn(net_task(stack))); | ||
| 126 | |||
| 127 | // And now we can use it! | ||
| 128 | |||
| 129 | let mut rx_buffer = [0; 4096]; | ||
| 130 | let mut tx_buffer = [0; 4096]; | ||
| 131 | let mut buf = [0; 4096]; | ||
| 132 | |||
| 133 | loop { | ||
| 134 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | ||
| 135 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | ||
| 136 | |||
| 137 | info!("Listening on TCP:1234..."); | ||
| 138 | if let Err(e) = socket.accept(1234).await { | ||
| 139 | warn!("accept error: {:?}", e); | ||
| 140 | continue; | ||
| 141 | } | ||
| 142 | |||
| 143 | info!("Received connection from {:?}", socket.remote_endpoint()); | ||
| 144 | |||
| 145 | loop { | ||
| 146 | let n = match socket.read(&mut buf).await { | ||
| 147 | Ok(0) => { | ||
| 148 | warn!("read EOF"); | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | Ok(n) => n, | ||
| 152 | Err(e) => { | ||
| 153 | warn!("read error: {:?}", e); | ||
| 154 | break; | ||
| 155 | } | ||
| 156 | }; | ||
| 157 | |||
| 158 | info!("rxd {:02x}", &buf[..n]); | ||
| 159 | |||
| 160 | match socket.write_all(&buf[..n]).await { | ||
| 161 | Ok(()) => {} | ||
| 162 | Err(e) => { | ||
| 163 | warn!("write error: {:?}", e); | ||
| 164 | break; | ||
| 165 | } | ||
| 166 | }; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | } | ||
