diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index cfd95b7b..f76e439a 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -12,6 +12,7 @@ flavors = [ ] [features] +defmt = ["dep:defmt", "embassy-usb?/defmt"] # Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-rp may major-bump (breaking) the PAC version. @@ -20,7 +21,7 @@ flavors = [ unstable-pac = [] # Enable nightly-only features -nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly"] +nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly", "dep:embassy-usb"] # Implement embedded-hal 1.0 alpha traits. # Implement embedded-hal-async traits if `nightly` is set as well. @@ -33,6 +34,7 @@ embassy-time = { version = "0.1.0", path = "../embassy-time", features = [ "tick embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-2"]} embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-usb = {version = "0.1.0", path = "../embassy-usb", optional = true } atomic-polyfill = "1.0.1" defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } @@ -43,8 +45,8 @@ cortex-m = "0.7.6" critical-section = "1.1" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } -rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] } -#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] } +rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] } +#rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 428855c7..a0328302 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -433,7 +433,7 @@ impl<'d, T: Pin> Flex<'d, T> { }); pin.io().ctrl().write(|w| { - w.set_funcsel(pac::io::vals::Gpio0CtrlFuncsel::SIO_0.0); + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0.0); }); } @@ -586,7 +586,7 @@ impl<'d, T: Pin> Drop for Flex<'d, T> { unsafe { self.pin.pad_ctrl().write(|_| {}); self.pin.io().ctrl().write(|w| { - w.set_funcsel(pac::io::vals::Gpio0CtrlFuncsel::NULL.0); + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL.0); }); } } diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 6db77b8c..aebbbf56 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -10,6 +10,8 @@ pub mod interrupt; pub mod spi; pub mod timer; pub mod uart; +#[cfg(feature = "nightly")] +pub mod usb; mod clocks; mod reset; @@ -80,6 +82,8 @@ embassy_hal_common::peripherals! { DMA_CH9, DMA_CH10, DMA_CH11, + + USB, } #[link_section = ".boot2"] @@ -110,3 +114,36 @@ pub fn init(_config: config::Config) -> Peripherals { peripherals } + +/// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes. +trait RegExt { + unsafe fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R; + unsafe fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R; + unsafe fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R; +} + +impl RegExt for pac::common::Reg { + unsafe fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + let ptr = (self.ptr() as *mut u8).add(0x1000) as *mut T; + ptr.write_volatile(val); + res + } + + unsafe fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + let ptr = (self.ptr() as *mut u8).add(0x2000) as *mut T; + ptr.write_volatile(val); + res + } + + unsafe fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + let ptr = (self.ptr() as *mut u8).add(0x3000) as *mut T; + ptr.write_volatile(val); + res + } +} diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs new file mode 100644 index 00000000..82eafdef --- /dev/null +++ b/embassy-rp/src/usb.rs @@ -0,0 +1,846 @@ +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::Ordering; +use core::task::Poll; + +use atomic_polyfill::compiler_fence; +use embassy_hal_common::into_ref; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb::driver::{self, EndpointAllocError, EndpointError, Event, Unsupported}; +use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; +use futures::future::poll_fn; +use futures::Future; + +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::{pac, peripherals, Peripheral, RegExt}; + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> crate::pac::usb::Usb; + fn dpram() -> crate::pac::usb_dpram::UsbDpram; + } +} + +pub trait Instance: sealed::Instance + 'static { + type Interrupt: Interrupt; +} + +impl crate::usb::sealed::Instance for peripherals::USB { + fn regs() -> pac::usb::Usb { + pac::USBCTRL_REGS + } + fn dpram() -> crate::pac::usb_dpram::UsbDpram { + pac::USBCTRL_DPRAM + } +} + +impl crate::usb::Instance for peripherals::USB { + type Interrupt = crate::interrupt::USBCTRL_IRQ; +} + +const EP_COUNT: usize = 16; +const EP_MEMORY_SIZE: usize = 4096; +const EP_MEMORY: *mut u8 = pac::USBCTRL_DPRAM.0; + +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static BUS_WAKER: AtomicWaker = NEW_AW; +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; + +struct EndpointBuffer { + addr: u16, + len: u16, + _phantom: PhantomData, +} + +impl EndpointBuffer { + const fn new(addr: u16, len: u16) -> Self { + Self { + addr, + len, + _phantom: PhantomData, + } + } + + fn read(&mut self, buf: &mut [u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts(EP_MEMORY.add(self.addr as _), buf.len()) }; + buf.copy_from_slice(mem); + compiler_fence(Ordering::SeqCst); + } + + fn write(&mut self, buf: &[u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts_mut(EP_MEMORY.add(self.addr as _), buf.len()) }; + mem.copy_from_slice(buf); + compiler_fence(Ordering::SeqCst); + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct EndpointData { + ep_type: EndpointType, // only valid if used + max_packet_size: u16, + used: bool, +} + +impl EndpointData { + const fn new() -> Self { + Self { + ep_type: EndpointType::Bulk, + max_packet_size: 0, + used: false, + } + } +} + +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_in: [EndpointData; EP_COUNT], + ep_out: [EndpointData; EP_COUNT], + ep_mem_free: u16, // first free address in EP mem, in bytes. +} + +impl<'d, T: Instance> Driver<'d, T> { + pub fn new(_usb: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { + into_ref!(irq); + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); + + let regs = T::regs(); + unsafe { + // zero fill regs + let p = regs.0 as *mut u32; + for i in 0..0x9c / 4 { + p.add(i).write_volatile(0) + } + + // zero fill epmem + let p = EP_MEMORY as *mut u32; + for i in 0..0x100 / 4 { + p.add(i).write_volatile(0) + } + + regs.usb_muxing().write(|w| { + w.set_to_phy(true); + w.set_softcon(true); + }); + regs.usb_pwr().write(|w| { + w.set_vbus_detect(true); + w.set_vbus_detect_override_en(true); + }); + regs.main_ctrl().write(|w| { + w.set_controller_en(true); + }); + } + + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + + Self { + phantom: PhantomData, + ep_in: [EndpointData::new(); EP_COUNT], + ep_out: [EndpointData::new(); EP_COUNT], + ep_mem_free: 0x180, // data buffer region + } + } + + fn on_interrupt(_: *mut ()) { + unsafe { + let regs = T::regs(); + //let x = regs.istr().read().0; + //trace!("USB IRQ: {:08x}", x); + + let ints = regs.ints().read(); + + if ints.bus_reset() { + regs.inte().write_clear(|w| w.set_bus_reset(true)); + BUS_WAKER.wake(); + } + if ints.dev_resume_from_host() { + regs.inte().write_clear(|w| w.set_dev_resume_from_host(true)); + BUS_WAKER.wake(); + } + if ints.dev_suspend() { + regs.inte().write_clear(|w| w.set_dev_suspend(true)); + BUS_WAKER.wake(); + } + if ints.setup_req() { + regs.inte().write_clear(|w| w.set_setup_req(true)); + EP_OUT_WAKERS[0].wake(); + } + + if ints.buff_status() { + let s = regs.buff_status().read(); + regs.buff_status().write_value(s); + + for i in 0..EP_COUNT { + if s.ep_in(i) { + EP_IN_WAKERS[i].wake(); + } + if s.ep_out(i) { + EP_OUT_WAKERS[i].wake(); + } + } + } + } + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result, driver::EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval={}, dir={:?}", + ep_type, + max_packet_size, + interval, + D::dir() + ); + + let alloc = match D::dir() { + UsbDirection::Out => &mut self.ep_out, + UsbDirection::In => &mut self.ep_in, + }; + + let index = alloc.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 { + return false; // reserved for control pipe + } + !ep.used + }); + + let (index, ep) = index.ok_or(EndpointAllocError)?; + assert!(!ep.used); + + if max_packet_size > 64 { + warn!("max_packet_size too high: {}", max_packet_size); + return Err(EndpointAllocError); + } + + // ep mem addrs must be 64-byte aligned, so there's no point in trying + // to allocate smaller chunks to save memory. + let len = 64; + + let addr = self.ep_mem_free; + if addr + len > EP_MEMORY_SIZE as _ { + warn!("Endpoint memory full"); + return Err(EndpointAllocError); + } + self.ep_mem_free += len; + + let buf = EndpointBuffer { + addr, + len, + _phantom: PhantomData, + }; + + trace!(" index={} addr={} len={}", index, buf.addr, buf.len); + + ep.ep_type = ep_type; + ep.used = true; + ep.max_packet_size = max_packet_size; + + let ep_type_reg = match ep_type { + EndpointType::Bulk => pac::usb_dpram::vals::EpControlEndpointType::BULK, + EndpointType::Control => pac::usb_dpram::vals::EpControlEndpointType::CONTROL, + EndpointType::Interrupt => pac::usb_dpram::vals::EpControlEndpointType::INTERRUPT, + EndpointType::Isochronous => pac::usb_dpram::vals::EpControlEndpointType::ISOCHRONOUS, + }; + + match D::dir() { + UsbDirection::Out => unsafe { + T::dpram().ep_out_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }) + }, + UsbDirection::In => unsafe { + T::dpram().ep_in_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }) + }, + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval, + }, + buf, + }) + } +} + +impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval) + } + + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let regs = T::regs(); + unsafe { + regs.inte().write(|w| { + w.set_bus_reset(true); + w.set_buff_status(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + w.set_setup_req(true); + }); + regs.int_ep_ctrl().write(|w| { + w.set_int_ep_active(0xFFFE); // all EPs + }); + regs.sie_ctrl().write(|w| { + w.set_ep0_int_1buf(true); + w.set_pullup_en(true); + }) + } + trace!("enabled"); + + ( + Bus { + phantom: PhantomData, + inited: false, + ep_out: self.ep_out, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + }, + ) + } +} + +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_out: [EndpointData; EP_COUNT], + inited: bool, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + type PollFuture<'a> = impl Future + 'a where Self: 'a; + + fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { + poll_fn(move |cx| unsafe { + BUS_WAKER.register(cx.waker()); + + if !self.inited { + self.inited = true; + return Poll::Ready(Event::PowerDetected); + } + + let regs = T::regs(); + let siestatus = regs.sie_status().read(); + + if siestatus.resume() { + regs.sie_status().write(|w| w.set_resume(true)); + return Poll::Ready(Event::Resume); + } + + if siestatus.bus_reset() { + regs.sie_status().write(|w| { + w.set_bus_reset(true); + w.set_setup_rec(true); + }); + regs.buff_status().write(|w| w.0 = 0xFFFF_FFFF); + regs.addr_endp().write(|w| w.set_address(0)); + + for i in 1..EP_COUNT { + T::dpram().ep_in_control(i - 1).modify(|w| w.set_enable(false)); + T::dpram().ep_out_control(i - 1).modify(|w| w.set_enable(false)); + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + return Poll::Ready(Event::Reset); + } + + if siestatus.suspended() { + regs.sie_status().write(|w| w.set_suspended(true)); + return Poll::Ready(Event::Suspend); + } + + // no pending event. Reenable all irqs. + regs.inte().write_set(|w| { + w.set_bus_reset(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + }); + Poll::Pending + }) + } + + #[inline] + fn set_address(&mut self, addr: u8) { + let regs = T::regs(); + trace!("setting addr: {}", addr); + unsafe { regs.addr_endp().write(|w| w.set_address(addr)) } + } + + fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) { + todo!(); + } + + fn endpoint_is_stalled(&mut self, _ep_addr: EndpointAddress) -> bool { + todo!(); + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("set_enabled {:?} {}", ep_addr, enabled); + if ep_addr.index() == 0 { + return; + } + + let n = ep_addr.index(); + match ep_addr.direction() { + UsbDirection::In => unsafe { + T::dpram().ep_in_control(n - 1).modify(|w| w.set_enable(enabled)); + T::dpram().ep_in_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, true); // first packet is DATA0, but PID is flipped before + }); + EP_IN_WAKERS[n].wake(); + }, + UsbDirection::Out => unsafe { + T::dpram().ep_out_control(n - 1).modify(|w| w.set_enable(enabled)); + + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + w.set_available(0, true); + }); + EP_OUT_WAKERS[n].wake(); + }, + } + } + + type EnableFuture<'a> = impl Future + 'a where Self: 'a; + + fn enable(&mut self) -> Self::EnableFuture<'_> { + async move {} + } + + type DisableFuture<'a> = impl Future + 'a where Self: 'a; + + fn disable(&mut self) -> Self::DisableFuture<'_> { + async move {} + } + + type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; + + fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { + async move { Err(Unsupported) } + } +} + +trait Dir { + fn dir() -> UsbDirection; + fn waker(i: usize) -> &'static AtomicWaker; +} + +pub enum In {} +impl Dir for In { + fn dir() -> UsbDirection { + UsbDirection::In + } + + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_IN_WAKERS[i] + } +} + +pub enum Out {} +impl Dir for Out { + fn dir() -> UsbDirection { + UsbDirection::Out + } + + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_OUT_WAKERS[i] + } +} + +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, + buf: EndpointBuffer, +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; + + fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { + async move { + trace!("wait_enabled IN WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = unsafe { T::dpram().ep_in_control(self.info.addr.index() - 1).read() }; + if val.enable() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("wait_enabled IN OK"); + } + } +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; + + fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { + async move { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = unsafe { T::dpram().ep_out_control(self.info.addr.index() - 1).read() }; + if val.enable() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("wait_enabled OUT OK"); + } + } +} + +impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { + type ReadFuture<'a> = impl Future> + 'a where Self: 'a; + + fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let val = poll_fn(|cx| unsafe { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as usize; + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + self.buf.read(&mut buf[..rx_len]); + + trace!("READ OK, rx_len = {}", rx_len); + + unsafe { + let pid = !val.pid(0); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + w.set_available(0, true); + }); + } + + Ok(rx_len) + } + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + type WriteFuture<'a> = impl Future> + 'a where Self: 'a; + + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + trace!("WRITE WAITING"); + + let index = self.info.addr.index(); + let val = poll_fn(|cx| unsafe { + EP_IN_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_in_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + self.buf.write(buf); + + unsafe { + let pid = !val.pid(0); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + w.set_available(0, true); + }); + } + + trace!("WRITE OK"); + + Ok(()) + } + } +} + +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, +} + +impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { + type SetupFuture<'a> = impl Future + 'a where Self: 'a; + type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; + type DataInFuture<'a> = impl Future> + 'a where Self: 'a; + type AcceptFuture<'a> = impl Future + 'a where Self: 'a; + type RejectFuture<'a> = impl Future + 'a where Self: 'a; + + fn max_packet_size(&self) -> usize { + 64 + } + + fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { + async move { + loop { + trace!("SETUP read waiting"); + let regs = T::regs(); + unsafe { regs.inte().write_set(|w| w.set_setup_req(true)) }; + + poll_fn(|cx| unsafe { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.sie_status().read().setup_rec() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + let mut buf = [0; 8]; + EndpointBuffer::::new(0, 8).read(&mut buf); + + let regs = T::regs(); + unsafe { + regs.sie_status().write(|w| w.set_setup_rec(true)); + + // set PID to 0, so (after toggling) first DATA is PID 1 + T::dpram().ep_in_buffer_control(0).write(|w| w.set_pid(0, false)); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_pid(0, false)); + } + + trace!("SETUP read ok"); + return buf; + } + } + } + + fn data_out<'a>(&'a mut self, buf: &'a mut [u8], _first: bool, _last: bool) -> Self::DataOutFuture<'a> { + async move { + unsafe { + let bufcontrol = T::dpram().ep_out_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + w.set_available(0, true); + }); + } + + trace!("control: data_out len={} first={} last={}", buf.len(), _first, _last); + let val = poll_fn(|cx| unsafe { + EP_OUT_WAKERS[0].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(0).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as _; + trace!("control data_out DONE, rx_len = {}", rx_len); + + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).read(&mut buf[..rx_len]); + + Ok(rx_len) + } + } + + fn data_in<'a>(&'a mut self, buf: &'a [u8], _first: bool, _last: bool) -> Self::DataInFuture<'a> { + async move { + trace!("control: data_in len={} first={} last={}", buf.len(), _first, _last); + + if buf.len() > 64 { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).write(buf); + + unsafe { + let bufcontrol = T::dpram().ep_in_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, buf.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, buf.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + w.set_available(0, true); + }); + } + + poll_fn(|cx| unsafe { + EP_IN_WAKERS[0].register(cx.waker()); + let bufcontrol = T::dpram().ep_in_buffer_control(0); + if bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("control: data_in DONE"); + + if _last { + // prepare status phase right away. + unsafe { + let bufcontrol = T::dpram().ep_out_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_available(0, true); + }); + } + } + + Ok(()) + } + } + + fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> { + async move { + trace!("control: accept"); + + unsafe { + let bufcontrol = T::dpram().ep_in_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + w.set_available(0, true); + }); + } + } + } + + fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> { + async move { + trace!("control: reject"); + + let regs = T::regs(); + unsafe { + regs.ep_stall_arm().write_set(|w| { + w.set_ep0_in(true); + w.set_ep0_out(true); + }); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_stall(true)); + T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); + } + } + } +} diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index d804a660..72a3a057 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -9,6 +9,10 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de 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"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } +embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] } defmt = "0.3" defmt-rtt = "0.3" @@ -25,3 +29,5 @@ byte-slice-cast = { version = "1.2.0", default-features = false } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-io = { version = "0.3.0", features = ["async", "defmt"] } +static_cell = "1.0.0" diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs new file mode 100644 index 00000000..2cb0010f --- /dev/null +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -0,0 +1,264 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Waker; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_rp::usb::Driver; +use embassy_rp::{interrupt, peripherals}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use embassy_usb::{Builder, Config, UsbDevice}; +use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; +use embedded_io::asynch::{Read, Write}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type MyDriver = Driver<'static, peripherals::USB>; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + STATIC_CELL.init_with(move || $val) + }}; +} + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { + device.run().await +} + +#[embassy_executor::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_executor::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_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBCTRL_IRQ); + let driver = Driver::new(p.USB, 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>, + } + let res: &mut Resources = singleton!(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))); + + 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 seed = 1234; // guaranteed random, chosen by a fair dice roll + + // Init network stack + let device = Device { mac_addr: our_mac_addr }; + 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; + } + }; + } + } +} + +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(&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(&self) -> [u8; 6] { + self.mac_addr + } +} diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs new file mode 100644 index 00000000..74be1f59 --- /dev/null +++ b/examples/rp/src/bin/usb_serial.rs @@ -0,0 +1,103 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_rp::interrupt; +use embassy_rp::usb::{Driver, Instance}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use embassy_usb_serial::{CdcAcmClass, State}; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBCTRL_IRQ); + let driver = Driver::new(p.USB, irq); + + // Create embassy-usb Config + 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; + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + None, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +}