diff --git a/Cargo.toml b/Cargo.toml index e4e6cec..da70a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,15 +16,10 @@ license = "MIT OR Apache-2.0" ch32-metapac = { features = [ "memory-x", "rt", -], git = "https://github.com/ch32-rs/ch32-metapac.git", tag = "ch32-data-0f143d8ce505711e4667002b42b8a34eb9c1d039" } -# ch32-metapac = { features = ["memory-x","rt"], path = "../ch32-data/build/ch32-metapac" } +], git = "https://github.com/ch32-rs/ch32-metapac", rev = "43066e18cf51106dff448f679dfff368a41431d7" } qingke = { version = "0.4.0", features = ["critical-section-impl"] } qingke-rt = { version = "0.4.0", optional = true } -# qingke-rt = { version = "0.4.0", path = "../qingke/qingke-rt", optional = true } -# qingke = { version = "0.4.0", path = "../qingke", features = [ -# "critical-section-impl", -# ] } # TODO: remove this embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ @@ -54,8 +49,7 @@ embassy-hal-internal = "0.2.0" [build-dependencies] ch32-metapac = { features = [ "metadata", -], git = "https://github.com/ch32-rs/ch32-metapac.git", tag = "ch32-data-0f143d8ce505711e4667002b42b8a34eb9c1d039" } -# ch32-metapac = { default-features = false, features = ["metadata"], path = "../ch32-data/build/ch32-metapac" } +], git = "https://github.com/ch32-rs/ch32-metapac", rev = "43066e18cf51106dff448f679dfff368a41431d7" } proc-macro2 = "1.0" quote = "1.0" diff --git a/README.md b/README.md index e3f2c59..14e7f74 100644 --- a/README.md +++ b/README.md @@ -32,17 +32,22 @@ others should work if you are careful as most peripherals are similar enough. For a full list of chip capabilities and peripherals, check the [ch32-data](https://github.com/ch32-rs/ch32-data) repository. -| Family | Status | Embassy | RCC | GPIO | UART*| SPI*| I2C | ADC | Timer(PWM) | EXTI*| RTC | DMA*| Delay | Others | -|--------|--------|---------|-----|------|------|-----|-----|-----|------------|------|-----|-----|-------| ------ | -| V2/V3 | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | RNG, SDIO | -| V1 | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | | ❓ | ✅ | | -| V0 | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | | ❓ | ✅ | | -| X0 | | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | ✅ | ✅ | ✅ | | ✅ | | | -| L1 | | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | ✅ | ✅ | ❓ | | ❓ | | | -| CH641 | | ✅ | ✅ | ✅ | ❓ | N/A | ❓ | ✅ | ✅ | ❓ | | ❓ | ✅ | ISP | -| CH643 | TODO | | | | | | | | | | | | | | +| Family | V2/V3 | V1 | V0 | X0 | L1 | CH641 | CH643 | +|-------------|--------|-----|-----|-----|-----|--------|--------| +| Embassy | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| RCC | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| GPIO | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| UART* | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | | +| SPI* | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | | +| I2C | ✅ | ✅ | ✅ | ❓ | ❓ | ❓ | | +| ADC | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| Timer(PWM) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| USB/OTG FS | ✅ | N/A | N/A | N/A | N/A | N/A | | +| USB/OTG HS | ❌ | N/A | N/A | N/A | N/A | N/A | | + - ✅ : Expected to work +- ❌ : Not implemented - ❓ : Not tested - `*` marks the async driver - TODO: I haven't got a dev board yet, help-wanted diff --git a/build.rs b/build.rs index df40651..37417c0 100644 --- a/build.rs +++ b/build.rs @@ -385,6 +385,9 @@ fn main() { (("sdio", "D6"), quote!(crate::sdio::D6Pin)), (("sdio", "D6"), quote!(crate::sdio::D7Pin)), (("sdio", "D8"), quote!(crate::sdio::D8Pin)), + // otg_fs + (("otg", "DP"), quote!(crate::otg_fs::DpPin)), + (("otg", "DM"), quote!(crate::otg_fs::DmPin)), // USB is splitted into multiple impls (("usbd", "DP"), quote!(crate::usbd::DpPin)), (("usbd", "DM"), quote!(crate::usbd::DmPin)), diff --git a/examples/ch32v208/Cargo.toml b/examples/ch32v208/Cargo.toml index d83b85b..5d00cc2 100644 --- a/examples/ch32v208/Cargo.toml +++ b/examples/ch32v208/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" [dependencies] ch32-hal = { path = "../../", features = [ "ch32v208wbu6", - "defmt", "embassy", "rt", ], default-features = false } diff --git a/examples/ch32v307/Cargo.toml b/examples/ch32v307/Cargo.toml index 51ae8d1..f92fddc 100644 --- a/examples/ch32v307/Cargo.toml +++ b/examples/ch32v307/Cargo.toml @@ -13,15 +13,18 @@ ch32-hal = { path = "../../", features = [ ] } embassy-executor = { version = "0.6.0", features = [ "integrated-timers", - "arch-riscv32", + "arch-spin", "executor-thread", ] } + +critical-section = "1.2.0" + embassy-time = "0.3.2" +embassy-usb = "0.3.0" nb = "1.1.0" qingke = "0.4.0" -# qingke-rt = { version = "0.4.0", path = "../../../qingke/qingke-rt" } -# qingke = { version = "0.4.0", path = "../../../qingke" } +qingke-rt = "0.4.0" # Not working for now # defmt = "0.3" @@ -39,6 +42,9 @@ ssd1306 = "0.9" edrv-bmp180 = "0.0.1" embedded-hal = "1.0.0" +# for usbfs_dfu +usb-dfu-target = "0.1.0" + [profile.release] strip = false # symbols are not flashed to the microcontroller, so don't strip them. lto = true diff --git a/examples/ch32v307/src/bin/usbfs_dfu.rs b/examples/ch32v307/src/bin/usbfs_dfu.rs new file mode 100644 index 0000000..05c5af1 --- /dev/null +++ b/examples/ch32v307/src/bin/usbfs_dfu.rs @@ -0,0 +1,162 @@ +#![no_std] +#![no_main] + +use panic_halt as _; +use ch32_hal::otg_fs::endpoint::EndpointDataBuffer; +use ch32_hal::otg_fs::{self, Driver}; +use ch32_hal::{self as hal, bind_interrupts, peripherals, Config}; +use embassy_executor::Spawner; +use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; +use embassy_usb::{Builder, Handler}; +use usb_dfu_target::consts::{DfuAttributes, DfuRequest}; +use usb_dfu_target::{DfuHandler, UsbDfuDevice}; + +bind_interrupts!(struct Irq { + OTG_FS => otg_fs::InterruptHandler; +}); + +const BLOCK_SIZE: usize = 64; + +struct DfuDemoDevice; + +impl DfuHandler for DfuDemoDevice { + fn write_data(&mut self, offset: usize, data: &[u8]) { + // Nothing + } + + fn complete_download(&mut self) { + // TODO: nothing + } + + fn upload(&self, buffer: &mut [u8], offset: usize) -> usize { + todo!() + } + + fn is_write_complete(&self) -> bool { + true + } +} + +#[embassy_executor::main(entry = "qingke_rt::entry")] +async fn main(spawner: Spawner) -> ! { + // setup clocks + let cfg = Config { + rcc: ch32_hal::rcc::Config::SYSCLK_FREQ_144MHZ_HSI, + ..Default::default() + }; + let p = hal::init(cfg); + hal::embassy::init(); + + /* USB DRIVER SECION */ + let mut buffer: [EndpointDataBuffer; 1] = core::array::from_fn(|_| EndpointDataBuffer::default()); + let driver = Driver::new(p.OTG_FS, p.PA12, p.PA11, &mut buffer); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0x6666, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB DFU Demo"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0x00; + config.device_sub_class = 0x00; + config.device_protocol = 0x00; + config.composite_with_iads = false; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut dfu_device_handler = DfuDemoDevice; + let mut request_handler = DfuRequestHandler { + inner: UsbDfuDevice::new(&mut dfu_device_handler), + }; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + let mut func = builder.function(0x00, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = { + use usb_dfu_target::consts::*; + iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None) + }; + let mut attr = DfuAttributes::empty(); + attr.set(DfuAttributes::CAN_DOWNLOAD, true); + alt.descriptor( + usb_dfu_target::consts::DESC_DFU_FUNCTIONAL, + &[ + attr.bits(), + 0xc4, + 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code + (BLOCK_SIZE & 0xff) as u8, + ((BLOCK_SIZE & 0xff00) >> 8) as u8, + 0x10, + 0x01, // DFU 1.1 + ], + ); + + drop(func); + builder.handler(&mut request_handler); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + usb_fut.await; + /* END USB DRIVER */ +} + +struct DfuRequestHandler<'h> { + inner: UsbDfuDevice<'h>, +} + +impl<'h> Handler for DfuRequestHandler<'h> { + fn control_out(&mut self, req: embassy_usb::control::Request, buf: &[u8]) -> Option { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + return None; + } + + match DfuRequest::try_from(req.request) { + Ok(req) => match self.inner.handle_control_out(req, buf) { + Ok(_) => Some(OutResponse::Accepted), + Err(_) => Some(OutResponse::Rejected), + }, + Err(_) => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>( + &'a mut self, + req: embassy_usb::control::Request, + buf: &'a mut [u8], + ) -> Option> { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + return None; + } + match DfuRequest::try_from(req.request) { + Ok(req) => match self.inner.handle_control_in(req, buf) { + Ok(buf) => Some(InResponse::Accepted(buf)), + Err(_) => Some(InResponse::Rejected), + }, + Err(_) => Some(InResponse::Rejected), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2ecad5a..421c237 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,8 +76,9 @@ pub mod spi; pub mod timer; pub mod usart; -#[cfg(usb)] -pub mod usb; + +#[cfg(otg)] +pub mod otg_fs; #[cfg(usbd)] pub mod usbd; diff --git a/src/macros.rs b/src/macros.rs index 608906a..8f647e5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,4 +1,5 @@ #![macro_use] +#![allow(unused_macros)] macro_rules! dma_trait { ($signal:ident, $instance:path$(, $mode:path)?) => { @@ -45,3 +46,75 @@ macro_rules! new_dma { }) }}; } + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} diff --git a/src/otg_fs/endpoint.rs b/src/otg_fs/endpoint.rs new file mode 100644 index 0000000..a32f103 --- /dev/null +++ b/src/otg_fs/endpoint.rs @@ -0,0 +1,566 @@ +use core::marker::PhantomData; +use core::task::Poll; + +use ch32_metapac::otg::vals::{EpRxResponse, EpTxResponse, UsbToken}; +use embassy_usb_driver::{Direction, EndpointAllocError, EndpointError, EndpointInfo}; +use futures::future::poll_fn; + +use super::marker::{Dir, In, Out}; +use super::{Instance, EP_MAX_PACKET_SIZE, EP_WAKERS}; +use crate::interrupt::typelevel::Interrupt; + +pub struct EndpointBufferAllocator<'d, const NR_EP: usize> { + ep_buffer: &'d mut [EndpointDataBuffer; NR_EP], + ep_next: usize, +} + +impl<'d, const NR_EP: usize> EndpointBufferAllocator<'d, NR_EP> { + pub fn new(ep_buffer: &'d mut [EndpointDataBuffer; NR_EP]) -> Self { + Self { ep_buffer, ep_next: 0 } + } + + pub fn alloc_endpoint( + &mut self, + max_packet_size: u16, + ) -> Result, embassy_usb_driver::EndpointAllocError> { + if self.ep_next >= NR_EP { + error!("out of endpoint buffers"); + return Err(EndpointAllocError); + } + + let ep_buf_idx = self.ep_next; + self.ep_next += 1; + + Ok(EndpointData { + max_packet_size, + buffer: unsafe { core::mem::transmute(&self.ep_buffer[ep_buf_idx] as *const EndpointDataBuffer) }, + }) + } +} + +pub struct EndpointData<'d> { + max_packet_size: u16, + buffer: &'d mut EndpointDataBuffer, +} + +impl<'d> EndpointData<'d> { + pub fn addr(&self) -> usize { + self.buffer.addr() + } +} + +#[repr(C, align(4))] +pub struct EndpointDataBuffer { + data: [u8; EP_MAX_PACKET_SIZE as usize], +} + +impl Default for EndpointDataBuffer { + fn default() -> Self { + unsafe { + EndpointDataBuffer { + data: core::mem::zeroed(), + } + } + } +} + +impl EndpointDataBuffer { + fn read_volatile(&self, buf: &mut [u8]) { + assert!(buf.len() <= self.data.len()); + let len = buf.len(); + + for i in 0..len { + buf[i] = unsafe { core::ptr::read_volatile(&self.data[i]) }; + } + } + + fn write_volatile(&mut self, buf: &[u8]) { + assert!(buf.len() <= self.data.len()); + let len = buf.len(); + + for i in 0..len { + unsafe { core::ptr::write_volatile(&mut self.data[i], buf[i]) }; + } + } + + fn addr(&self) -> usize { + self.data.as_ptr() as usize + } +} + +/// USB endpoint. +pub struct Endpoint<'d, T, D> { + _phantom: PhantomData<&'d (T, D)>, + info: EndpointInfo, + data: EndpointData<'d>, +} + +impl<'d, T: Instance, D: Dir> Endpoint<'d, T, D> { + pub fn new(info: EndpointInfo, data: EndpointData<'d>) -> Self { + T::regs() + .uep_dma(info.addr.index() as usize) + .write_value(data.buffer.addr() as u32); + Self { + _phantom: PhantomData, + info, + data, + } + } + + async fn wait_enabled_internal(&mut self) { + poll_fn(|ctx| { + EP_WAKERS[self.info.addr.index() as usize].register(ctx.waker()); + if self.is_enabled() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("endpoint {} enabled", self.info.addr.index()); + } + + fn is_enabled(&self) -> bool { + let regs = T::regs(); + match (self.info.addr.index(), self.info.addr.direction()) { + (4, Direction::In) => regs.uep4_1_mod().read().uep4_tx_en(), + (4, Direction::Out) => regs.uep4_1_mod().read().uep4_rx_en(), + (1, Direction::In) => regs.uep4_1_mod().read().uep1_tx_en(), + (1, Direction::Out) => regs.uep4_1_mod().read().uep1_rx_en(), + + (2, Direction::In) => regs.uep2_3_mod().read().uep2_tx_en(), + (2, Direction::Out) => regs.uep2_3_mod().read().uep2_rx_en(), + (3, Direction::In) => regs.uep2_3_mod().read().uep3_tx_en(), + (3, Direction::Out) => regs.uep2_3_mod().read().uep3_rx_en(), + + (5, Direction::In) => regs.uep5_6_mod().read().uep5_tx_en(), + (5, Direction::Out) => regs.uep5_6_mod().read().uep5_rx_en(), + (6, Direction::In) => regs.uep5_6_mod().read().uep6_tx_en(), + (6, Direction::Out) => regs.uep5_6_mod().read().uep6_rx_en(), + + (7, Direction::In) => regs.uep7_mod().read().uep7_tx_en(), + (7, Direction::Out) => regs.uep7_mod().read().uep7_rx_en(), + + _ => panic!("Unsupported EP"), + } + } +} + +impl<'d, T: Instance, D: Dir> embassy_usb_driver::Endpoint for Endpoint<'d, T, D> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + self.wait_enabled_internal().await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + trace!("endpoint {} IN", self.info.addr.index()); + if !self.is_enabled() { + error!("write to disabled ep {}", self.info.addr.index()); + return Err(EndpointError::Disabled); + } + + let ep = self.info.addr.index(); + let regs = T::regs(); + + // Write buffer, txLen, and ACK + self.data.buffer.write_volatile(buf); + regs.uep_t_len(ep).write_value(buf.len() as u8); + regs.uep_tx_ctrl(ep).modify(|v| { + v.set_t_tog(!v.t_tog()); + v.set_mask_t_res(EpTxResponse::ACK); + }); + + // Wait for TX complete + let tx_result = poll_fn(|ctx| { + super::EP_WAKERS[ep].register(ctx.waker()); + + let ret = if regs.int_fg().read().transfer() { + let status = regs.int_st().read(); + if status.mask_uis_endp() as usize == ep { + let token = status.mask_token(); + let ret = match token { + UsbToken::IN => { + regs.uep_tx_ctrl(ep).modify(|v| { + v.set_mask_t_res(EpTxResponse::NAK); + }); + Poll::Ready(Ok(())) + } + token => { + error!("Unexpected USB Token {}", token.to_bits()); + Poll::Ready(Err(EndpointError::Disabled)) + } + }; + regs.int_fg().write(|v| v.set_transfer(true)); + ret + } else { + Poll::Pending + } + } else { + Poll::Pending + }; + unsafe { T::Interrupt::enable() } + ret + }) + .await; + + tx_result + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("endpoint {} OUT", self.info.addr); + if !self.is_enabled() { + return Err(EndpointError::Disabled); + } + + let ep = self.info.addr.index(); + let regs = T::regs(); + + // Tx Ctrl should be NAK + + regs.uep_rx_ctrl(ep).modify(|v| { + v.set_r_tog(!v.r_tog()); + v.set_mask_r_res(EpRxResponse::ACK); + }); + + // poll for packet + let bytes_read = poll_fn(|ctx| { + super::EP_WAKERS[ep].register(ctx.waker()); + + let ret = if regs.int_fg().read().transfer() { + let status = regs.int_st().read(); + if status.mask_uis_endp() as usize == ep { + let ret = match status.mask_token() { + UsbToken::OUT => { + // upper bits are reserved (0) + let len = regs.rx_len().read().0 as usize; + + // Assertion exists because looks like embassy-usb expects no partial reads. + // https://github.com/embassy-rs/embassy/blob/6e0b08291b63a0da8eba9284869d1d046bc5dabb/embassy-usb/src/lib.rs#L408 + debug_assert_eq!(len, buf.len()); + if len == buf.len() { + self.data.buffer.read_volatile(&mut buf[..len]); + Poll::Ready(Ok(len)) + } else { + Poll::Ready(Err(EndpointError::BufferOverflow)) + } + } + token => { + error!("Unexpected USB Token {}", token.to_bits()); + Poll::Ready(Err(EndpointError::Disabled)) + } + }; + + regs.uep_rx_ctrl(ep).modify(|v| { + v.set_mask_r_res(EpRxResponse::NAK); + }); + regs.int_fg().write(|v| v.set_transfer(true)); + ret + } else { + Poll::Pending + } + } else { + Poll::Pending + }; + unsafe { T::Interrupt::enable() }; + ret + }) + .await?; + + Ok(bytes_read) + } +} + +pub struct ControlPipe<'d, T> { + ep0_buf: EndpointData<'d>, + _phantom: PhantomData, +} + +impl<'d, T: Instance> ControlPipe<'d, T> { + pub fn new(ep0_buf: EndpointData<'d>) -> Self { + Self { + ep0_buf, + _phantom: PhantomData, + } + } +} + +impl<'d, T> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> +where + T: Instance, +{ + fn max_packet_size(&self) -> usize { + usize::from(EP_MAX_PACKET_SIZE) + } + + async fn setup(&mut self) -> [u8; 8] { + poll_fn(move |ctx| { + super::EP_WAKERS[0].register(ctx.waker()); + let poll_result = { + let regs = T::regs(); + if regs.int_fg().read().transfer() { + let int_status = regs.int_st().read(); + + // assume the setup packet is always for endpoint 0. + // the hardware doesn't seem to set mask_uis_endp to 0 + // when the setup packet arrives. + match int_status.mask_token() { + UsbToken::SETUP => { + // SETUP packet token + regs.uep_tx_ctrl(0).write(|w| { + w.set_mask_t_res(EpTxResponse::NAK); + }); + regs.uep_rx_ctrl(0).write(|w| { + w.set_mask_r_res(EpRxResponse::NAK); + }); + let mut data = [0u8; 8]; + + self.ep0_buf.buffer.read_volatile(&mut data[..8]); + + regs.int_fg().write(|v| v.set_transfer(true)); + // Clear Flag + Poll::Ready(data) + } + _ => Poll::Pending, + } + } else { + Poll::Pending + } + }; + unsafe { T::Interrupt::enable() }; + poll_result + }) + .await + } + + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + assert!(first && last, "TODO support chunking"); + + let regs = T::regs(); + + if buf.len() > self.ep0_buf.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + regs.uep_rx_ctrl(0).modify(|v| { + v.set_r_tog(!v.r_tog()); + v.set_mask_r_res(EpRxResponse::ACK); + }); + + // poll for packet + let bytes_read = poll_fn(|ctx| { + super::EP_WAKERS[0].register(ctx.waker()); + + let ret = if regs.int_fg().read().transfer() { + let status = regs.int_st().read(); + if status.mask_uis_endp() == 0 { + let ret = match status.mask_token() { + UsbToken::RSVD | UsbToken::IN => unreachable!(), + UsbToken::SETUP => Poll::Ready(Err(EndpointError::Disabled)), + UsbToken::OUT => { + let len = regs.rx_len().read().0 as usize; + // https://github.com/embassy-rs/embassy/blob/6e0b08291b63a0da8eba9284869d1d046bc5dabb/embassy-usb/src/lib.rs#L408 + // Embassy expects the whole buffer to be filled + if len == buf.len() { + self.ep0_buf.buffer.read_volatile(&mut buf[..len]); + Poll::Ready(Ok(len)) + } else { + Poll::Ready(Err(EndpointError::BufferOverflow)) + } + } + }; + + regs.int_fg().write(|v| v.set_transfer(true)); + ret + } else { + Poll::Pending + } + } else { + Poll::Pending + }; + unsafe { T::Interrupt::enable() }; + ret + }) + .await?; + + regs.uep_rx_ctrl(0).modify(|v| { + v.set_mask_r_res(EpRxResponse::NAK); + }); + + Ok(bytes_read) + } + + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + assert!(first && last, "Chunking is not supported"); + + let regs = T::regs(); + + if data.len() > self.ep0_buf.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + self.ep0_buf.buffer.write_volatile(data); + + // TODO: manual is wrong here, t_len(3) should be a u16 + regs.uep_t_len(0).write_value(data.len() as u8); + + regs.uep_tx_ctrl(0).modify(|v| { + v.set_mask_t_res(EpTxResponse::ACK); + + // First control in should always be DATA1? + if first { + v.set_t_tog(true); + } else { + v.set_t_tog(!v.t_tog()); + } + }); + + // Poll for last packet to finsh transfer + poll_fn(|ctx| { + super::EP_WAKERS[0].register(ctx.waker()); + let poll_result = { + let interrupt_flags = regs.int_fg().read(); + if interrupt_flags.transfer() { + let transfer_result = { + let status = regs.int_st().read(); + if status.mask_uis_endp() == 0 { + match status.mask_token() { + UsbToken::IN => { + regs.uep_tx_ctrl(0).modify(|v| { + v.set_mask_t_res(EpTxResponse::NAK); + }); + Poll::Ready(Ok(())) + } + token => { + error!("unexpected USB Token {}, aborting", token.to_bits()); + Poll::Ready(Err(EndpointError::Disabled)) + } + } + } else { + Poll::Pending + } + }; + + regs.int_fg().write(|v| v.set_transfer(true)); + transfer_result + } else { + Poll::Pending + } + }; + unsafe { T::Interrupt::enable() }; + poll_result + }) + .await?; + + if last { + regs.uep_rx_ctrl(0).modify(|v| { + // Set RX to true to expect the STATUS (OUT) packet + v.set_mask_r_res(EpRxResponse::ACK); + }); + // Expect the empty OUT token for status + poll_fn(|ctx| { + super::EP_WAKERS[0].register(ctx.waker()); + + let poll_res = if regs.int_fg().read().transfer() { + let status = regs.int_st().read(); + let res = if status.mask_uis_endp() == 0 { + match status.mask_token() { + UsbToken::OUT => { + if regs.rx_len().read().0 != 0 { + error!("Expected 0 len OUT stage, found non-zero len, aborting"); + Poll::Ready(Err(EndpointError::BufferOverflow)) + } else { + // Set the EP back to NAK so that we are "not ready to recieve" + regs.uep_rx_ctrl(0).write(|v| { + v.set_r_tog(true); + v.set_mask_r_res(EpRxResponse::NAK); + }); + Poll::Ready(Ok(())) + } + } + _ => { + error!("Got {} instead of OUT token", status.mask_token().to_bits()); + Poll::Ready(Err(EndpointError::Disabled)) + } + } + } else { + Poll::Pending + }; + // Clear transffer flag + regs.int_fg().write(|v| v.set_transfer(true)); + + res + } else { + Poll::Pending + }; + + unsafe { T::Interrupt::enable() }; + + poll_res + }) + .await?; + } + + Ok(()) + } + + async fn accept(&mut self) { + let regs = T::regs(); + + // Rx should already be NAK + regs.uep_t_len(0).write_value(0); + regs.uep_tx_ctrl(0).write(|v| { + v.set_t_tog(true); + v.set_mask_t_res(EpTxResponse::ACK); + }); + + poll_fn(|ctx| { + super::EP_WAKERS[0].register(ctx.waker()); + + let res = if regs.int_fg().read().transfer() { + let status = regs.int_st().read(); + assert_eq!(status.mask_uis_endp(), 0); + match status.mask_token() { + UsbToken::IN => { + // Maybe stall? and unstall when we actually set addr + regs.uep_tx_ctrl(0).write(|v| { + v.set_mask_t_res(EpTxResponse::NAK); + v.set_t_tog(true); + }); + regs.int_fg().write(|v| v.set_transfer(true)); + } + token => panic!("Token = {}", token.to_bits()), + } + Poll::Ready(()) + } else { + Poll::Pending + }; + unsafe { T::Interrupt::enable() }; + res + }) + .await; + } + + async fn reject(&mut self) { + let regs = T::regs(); + regs.uep_rx_ctrl(0).write(|v| { + v.set_mask_r_res(EpRxResponse::STALL); + }); + regs.uep_tx_ctrl(0).write(|v| { + v.set_mask_t_res(EpTxResponse::STALL); + }); + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + T::regs().dev_ad().write(|v| { + v.set_mask_usb_addr(addr); + }); + } +} diff --git a/src/otg_fs/marker.rs b/src/otg_fs/marker.rs new file mode 100644 index 0000000..8a8fdb0 --- /dev/null +++ b/src/otg_fs/marker.rs @@ -0,0 +1,24 @@ +use embassy_usb_driver::Direction; + + +/// USB Direction Trait +pub trait Dir { + /// Returns the direction value. + fn dir() -> Direction; +} + +/// Marker type for the "IN" direction. +pub struct In; +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Marker type for the "OUT" direction. +pub struct Out; +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} diff --git a/src/otg_fs/mod.rs b/src/otg_fs/mod.rs new file mode 100644 index 0000000..e46059b --- /dev/null +++ b/src/otg_fs/mod.rs @@ -0,0 +1,455 @@ +//! USB_FS and OTG_FS device mode peripheral driver +//! Note that this currently only implements device mode +//! +//!
+//! There's a lot of TODOs and panics where things are not implemented +//!
+//! +//! List of things that is tested +//! Untested but expected to work items are noted as well +//! +//! Control Pipe: +//! - [x] Recieve `SETUP` packet (Host -> Dev) +//! - [x] Send `IN` packet (Dev -> Host). +//! - [x] Recieve `OUT` packet (Host -> Dev). +//! +//! Other Endpoints: +//! - [x] Interrupt Out +//! - [x] Interrupt In +//! - [ ] Bulk Out (Expected to work but not tested) +//! - [ ] Bulk In (Expected to work but not tested) +//! +//! Other Features: +//! - [ ] Set endpoint stall +//! - [ ] Get endpoint stall status +//! - [ ] Remote wakeup +//! + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use ch32_metapac::otg::vals::{EpRxResponse, EpTxResponse, UsbToken}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver::{self as driver, Direction, EndpointAddress, EndpointInfo, EndpointType, Event}; +use endpoint::{ControlPipe, Endpoint, EndpointBufferAllocator, EndpointDataBuffer}; +use marker::{Dir, In, Out}; + +use crate::gpio::{AFType, Speed}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, peripherals, Peripheral, RccPeripheral}; + +pub mod endpoint; +pub mod marker; + +// TODO: We technically support 16, but we only allow 8 for now (0, 1-7). +const MAX_NR_EP: usize = 8; +const EP_MAX_PACKET_SIZE: u16 = 64; + +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static BUS_WAKER: AtomicWaker = NEW_AW; +static EP_WAKERS: [AtomicWaker; MAX_NR_EP] = [NEW_AW; MAX_NR_EP]; + +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let int_fg = regs.int_fg().read(); + + if int_fg.fifo_ov() { + panic!("overflow"); + } + + // All the interrupt we handle + if int_fg.suspend() || int_fg.transfer() || int_fg.bus_rst() { + T::Interrupt::disable(); + + // Bus stuff we wakup bus + if int_fg.bus_rst() || int_fg.suspend() { + BUS_WAKER.wake(); + } + + if int_fg.transfer() { + let status = regs.int_st().read(); + + let token = status.mask_token(); + match token { + UsbToken::IN | UsbToken::OUT => { + let ep = status.mask_uis_endp(); + + if ep as usize >= MAX_NR_EP { + error!("[USBFS] Unexpected EP: {} got token: {:#x}", ep, token.to_bits()); + } else { + EP_WAKERS[ep as usize].wake(); + } + } + UsbToken::SETUP => { + EP_WAKERS[0].wake(); + } + UsbToken::RSVD => panic!("rsvd token"), + } + } + } + + // Clear the host sof, we are in device mode.... It's garbage + if int_fg.hst_sof() { + regs.int_fg().write(|v| v.set_hst_sof(true)); + } + } +} + +pub struct Driver<'d, T: Instance, const NR_EP: usize> { + phantom: PhantomData<&'d mut T>, + allocator: EndpointBufferAllocator<'d, NR_EP>, + next_ep_addr: u8, +} + +impl<'d, T, const NR_EP: usize> Driver<'d, T, NR_EP> +where + T: Instance, +{ + pub fn new( + _usb: impl Peripheral

+ 'd, + // _irq: impl interrupt::typelevel::Binding> + 'd, + dp: impl Peripheral

+ 'd, + dm: impl Peripheral

+ 'd, + ep_buffer: &'d mut [EndpointDataBuffer; NR_EP], + ) -> Self { + assert!(ep_buffer.len() > 0); + let dp = dp.into_ref(); + let dm = dm.into_ref(); + + dp.set_as_af_output(AFType::OutputPushPull, Speed::High); + dm.set_as_af_output(AFType::OutputPushPull, Speed::High); + + T::enable_and_reset(); + + let allocator = EndpointBufferAllocator::new(ep_buffer); + + Self { + phantom: PhantomData, + allocator, + // 0 is reserved for the control endpoint + next_ep_addr: 1, + } + } + + fn alloc_ep_address(&mut self) -> u8 { + if self.next_ep_addr as usize >= MAX_NR_EP { + panic!("ep addr overflow") + } + + let addr = self.next_ep_addr; + self.next_ep_addr += 1; + addr + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, driver::EndpointAllocError> { + let ep_addr = self.alloc_ep_address(); + let data = self.allocator.alloc_endpoint(max_packet_size)?; + + Ok(Endpoint::new( + EndpointInfo { + // todo fix in embassy usb driver ep_addr should be u8 with top bit unset + addr: EndpointAddress::from_parts(ep_addr as usize, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + data, + )) + } +} + +impl<'d, T: Instance, const NR_EP: usize> driver::Driver<'d> for Driver<'d, T, NR_EP> { + 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_out( + &mut self, + ep_type: driver::EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint::(ep_type, max_packet_size, interval_ms) + } + + fn alloc_endpoint_in( + &mut self, + ep_type: driver::EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint::(ep_type, max_packet_size, interval_ms) + } + + fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + assert!(control_max_packet_size <= EP_MAX_PACKET_SIZE); + let regs = T::regs(); + + regs.uep_rx_ctrl(0).write(|v| v.set_mask_r_res(EpRxResponse::NAK)); + + regs.ctrl().write(|w| { + w.set_clr_all(true); + w.set_reset_sie(true); + }); + + embassy_time::block_for(embassy_time::Duration::from_micros(10)); + + // Clear all + regs.ctrl().write(|_| {}); + + regs.int_en().write(|w| { + // w.set_dev_nak(true); + // w.set_fifo_ov(true); + + // Host SOF is ignored, not our usecase here + + w.set_suspend(true); + w.set_transfer(true); + w.set_bus_rst(true); + }); + + regs.ctrl().write(|w| { + w.set_int_busy(true); + w.set_dma_en(true); + w.set_dev_pu_en(true); + }); + + let ep0_buf = self.allocator.alloc_endpoint(control_max_packet_size).unwrap(); + + regs.uep_dma(0).write_value(ep0_buf.addr() as u32); + + regs.uep_rx_ctrl(0).write(|w| w.set_mask_r_res(EpRxResponse::ACK)); + regs.uep_tx_ctrl(0).write(|w| w.set_mask_t_res(EpTxResponse::NAK)); + + // Hookup the bus on start? + regs.udev_ctrl().write(|w| { + // pd is for HOST + w.set_pd_dis(true); + w.set_port_en(true); + }); + + // Initialize the bus so that it signals that power is available + // usbd.rs does BUS_WAKER.wake(), but it doesn't seem necessary + BUS_WAKER.wake(); + + critical_section::with(|_cs| { + T::Interrupt::unpend(); + unsafe { + T::Interrupt::enable(); + } + }); + + ( + Bus { + _phantom: PhantomData, + inited: false, + }, + ControlPipe::new(ep0_buf), + ) + } +} + +pub struct Bus<'d, T> { + _phantom: PhantomData<&'d T>, + inited: bool, +} + +impl<'d, T: Instance> Bus<'d, T> { + fn bus_reset(&mut self) { + let regs = T::regs(); + + // Reset device address + regs.dev_ad().write(|v| { + v.set_mask_usb_addr(0); + }); + + // Reset endpoint state on bus reset + // EP0 get ACK/NAK so it can recieve setup + regs.uep_rx_ctrl(0).write(|v| v.set_mask_r_res(EpRxResponse::ACK)); + regs.uep_tx_ctrl(0).write(|v| v.set_mask_t_res(EpTxResponse::NAK)); + + // Mark all other EPs as NAK + for i in 1..=7 { + regs.uep_rx_ctrl(i).write(|v| v.set_mask_r_res(EpRxResponse::NAK)); + regs.uep_tx_ctrl(i).write(|v| v.set_mask_t_res(EpTxResponse::NAK)); + } + } +} + +impl<'d, T> driver::Bus for Bus<'d, T> +where + T: Instance, +{ + async fn enable(&mut self) { + trace!("enable") + } + + async fn disable(&mut self) { + trace!("disable") + } + + async fn poll(&mut self) -> embassy_usb_driver::Event { + // TODO: VBUS detection + if !self.inited { + self.inited = true; + return Event::PowerDetected; + } + + poll_fn(|ctx| { + BUS_WAKER.register(ctx.waker()); + + let poll_res = { + let regs = T::regs(); + let interrupt_flags = regs.int_fg().read(); + + // Either Suspend or Resume has happened + if interrupt_flags.suspend() { + // Clear suspend flag + regs.int_fg().write(|v| v.set_suspend(true)); + + if regs.mis_st().read().suspend() { + Poll::Ready(Event::Suspend) + } else { + Poll::Ready(Event::Resume) + } + } else if interrupt_flags.bus_rst() { + trace!("bus: reset"); + self.bus_reset(); + + regs.int_fg().write(|v| { + v.set_bus_rst(true); + }); + + Poll::Ready(Event::Reset) + } else { + Poll::Pending + } + }; + unsafe { T::Interrupt::enable() }; + poll_res + }) + .await + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + #[cfg(feature="defmt")] + trace!( + "[USBFS] Endpoint: {}, {}: Set enable={}", + ep_addr.index(), + ep_addr.direction(), + enabled + ); + let regs = T::regs(); + + match (ep_addr.index(), ep_addr.direction()) { + (4, Direction::In) => regs.uep4_1_mod().modify(|v| { + v.set_uep4_tx_en(enabled); + }), + (4, Direction::Out) => regs.uep4_1_mod().modify(|v| { + v.set_uep4_rx_en(enabled); + }), + (1, Direction::In) => regs.uep4_1_mod().modify(|v| { + v.set_uep1_tx_en(enabled); + }), + (1, Direction::Out) => regs.uep4_1_mod().modify(|v| { + v.set_uep1_rx_en(enabled); + }), + + (2, Direction::In) => regs.uep2_3_mod().modify(|v| { + v.set_uep2_tx_en(enabled); + }), + (2, Direction::Out) => regs.uep2_3_mod().modify(|v| { + v.set_uep2_rx_en(enabled); + }), + (3, Direction::In) => regs.uep2_3_mod().modify(|v| { + v.set_uep3_tx_en(enabled); + }), + (3, Direction::Out) => regs.uep2_3_mod().modify(|v| { + v.set_uep3_rx_en(enabled); + }), + + (5, Direction::In) => regs.uep5_6_mod().modify(|v| { + v.set_uep5_tx_en(enabled); + }), + (5, Direction::Out) => regs.uep5_6_mod().modify(|v| { + v.set_uep5_rx_en(enabled); + }), + (6, Direction::In) => regs.uep5_6_mod().modify(|v| { + v.set_uep6_tx_en(enabled); + }), + (6, Direction::Out) => regs.uep5_6_mod().modify(|v| { + v.set_uep6_rx_en(enabled); + }), + + (7, Direction::In) => regs.uep7_mod().modify(|v| { + v.set_uep7_tx_en(enabled); + }), + (7, Direction::Out) => regs.uep7_mod().modify(|v| { + v.set_uep7_rx_en(enabled); + }), + + _ => { + #[cfg(feature = "defmt")] + defmt::panic!("setting non-existent endpoint {} to {}", ep_addr, enabled); + #[cfg(not(feature = "defmt"))] + panic!() + } + } + EP_WAKERS[ep_addr.index() as usize].wake(); + } + + fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) { + todo!() + } + + fn endpoint_is_stalled(&mut self, _ep_addr: EndpointAddress) -> bool { + todo!() + } + + async fn remote_wakeup(&mut self) -> Result<(), embassy_usb_driver::Unsupported> { + todo!() + } +} + +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::otg::Usbd; +} + +/// OTG_FS peripheral instance +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static { + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_peripheral!( + (otg, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::otg::Usbd { + // datasheet + unsafe { crate::pac::otg::Usbd::from_ptr(crate::pac::OTG_FS.as_ptr()) } + } + } + + impl Instance for peripherals::$inst { + type Interrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; + } + }; +); diff --git a/src/usb/mod.rs b/src/usb/mod.rs index fe21f63..08efdee 100644 --- a/src/usb/mod.rs +++ b/src/usb/mod.rs @@ -6,3 +6,5 @@ //! - USB_OTG_FS, USBFS with OTG //! - USBHS, CH32V305/CH32V307 //! - USBSS, CH569/CH565 - won't be implemented here as CH5xx series is not supported + +pub mod otg_fs;