From db11231f492be7eac1aa78e4644196cdc16864c8 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Thu, 9 Sep 2021 08:55:30 +0100 Subject: [PATCH] Implement usb_device for rp2040 (#98) Implement usb_device for rp2040 Limitations - RP2040-E5 (Device enumeration workaround) is not implemented - Suspend/resume is not implemented - VBus detection is not tested and may not be fully implemented --- boards/pico/Cargo.toml | 4 +- boards/pico/examples/pico_usb_serial.rs | 87 ++++ rp2040-hal/Cargo.toml | 9 +- rp2040-hal/src/usb.rs | 544 +++++++++++++++++++++++- 4 files changed, 639 insertions(+), 5 deletions(-) create mode 100644 boards/pico/examples/pico_usb_serial.rs diff --git a/boards/pico/Cargo.toml b/boards/pico/Cargo.toml index 37da581..e95165f 100644 --- a/boards/pico/Cargo.toml +++ b/boards/pico/Cargo.toml @@ -14,6 +14,8 @@ cortex-m = "0.7.2" rp2040-hal = { path = "../../rp2040-hal", version = "0.2.0"} cortex-m-rt = { version = "0.6.14", optional = true } embedded-time = "0.12.0" +usb-device="0.2.8" +usbd-serial = "0.1.1" [dev-dependencies] panic-halt= "0.2.0" @@ -22,4 +24,4 @@ rp2040-boot2 = "0.1.2" [features] default = ["rt"] -rt = ["cortex-m-rt","rp2040-hal/rt"] \ No newline at end of file +rt = ["cortex-m-rt","rp2040-hal/rt"] diff --git a/boards/pico/examples/pico_usb_serial.rs b/boards/pico/examples/pico_usb_serial.rs new file mode 100644 index 0000000..877fa52 --- /dev/null +++ b/boards/pico/examples/pico_usb_serial.rs @@ -0,0 +1,87 @@ +//! Creates a USB Serial device on a Pico board. +//! +//! This will create a USB Serial device echoing anything it receives converting to caps the ASCII +//! alphabetical caracters. +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use panic_halt as _; +use pico::{ + hal::{clocks::init_clocks_and_plls, pac, timer::Timer, usb::UsbBus, watchdog::Watchdog}, + XOSC_CRYSTAL_FREQ, +}; +use usb_device::{class_prelude::*, prelude::*}; +use usbd_serial::SerialPort; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let timer = Timer::new(pac.TIMER); + let usb_bus = UsbBusAllocator::new(UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + let mut serial = SerialPort::new(&usb_bus); + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .max_packet_size_0(64) + .device_class(2) // from: https://www.usb.org/defined-class-codes + .build(); + + let mut said_hello = false; + loop { + if !said_hello && timer.get_counter() >= 2_000_000 { + said_hello = true; + let _ = serial.write(b"HelloWorld!\r\n"); + } + + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + let _ = serial.read(&mut buf).map(|count| { + if count == 0 { + return; + } + + // Echo back in upper case + buf.iter_mut().take(count).for_each(|c| { + if let 0x61..=0x7a = *c { + *c &= !0x20; + } + }); + + let mut wr_ptr = &buf[..count]; + while !wr_ptr.is_empty() { + let _ = serial.write(wr_ptr).map(|len| { + wr_ptr = &wr_ptr[len..]; + }); + } + }); + } +} diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 0a93c2a..1349432 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -11,16 +11,19 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7.2" -embedded-hal = { version = "0.2.5", features=["unproven"] } +embedded-hal = { version = "0.2.5", features = ["unproven"] } embedded-time = "0.12.0" nb = "1.0" -rp2040-pac = "0.1.4" +rp2040-pac = { git = "https://github.com/rp-rs/rp2040-pac", rev = "0226d2c28e8dca03475d6783cbdf07f535859c23" } paste = "1.0" +void = { version = "1.0.2", default-features = false } +usb-device = "0.2.8" +itertools = { version = "0.10.1", default-features = false } [dev-dependencies] cortex-m-rt = "0.6.14" panic-halt = "0.2.0" -rp2040-boot2 = { git = "https://github.com/rp-rs/rp2040-boot2-rs", rev = "67400f600b192e950b58df79ddc9b57ff209ef08" } +rp2040-boot2 = { git = "https://github.com/rp-rs/rp2040-boot2-rs", rev = "67400f600b192e950b58df79ddc9b57ff209ef08" } hd44780-driver = "0.4.0" [features] diff --git a/rp2040-hal/src/usb.rs b/rp2040-hal/src/usb.rs index 332df6e..7e26e31 100644 --- a/rp2040-hal/src/usb.rs +++ b/rp2040-hal/src/usb.rs @@ -1,3 +1,545 @@ //! Universal Serial Bus (USB) // See [Chapter 4 Section 1](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details -// TODO +//! ## Usage +//! +//! Initialize the Usb Bus forcing the VBUS detection. +//! ```no_run +//! use rp2040_hal::{clocks::init_clocks_and_plls, pac, sio::Sio, usb::UsbBus, watchdog::Watchdog}; +//! use usb_device::class_prelude::UsbBusAllocator; +//! +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! let mut pac = pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(pac.SIO); +//! let mut watchdog = Watchdog::new(pac.WATCHDOG); +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! pac.XOSC, +//! pac.CLOCKS, +//! pac.PLL_SYS, +//! pac.PLL_USB, +//! &mut pac.RESETS, +//! &mut watchdog +//! ).ok().unwrap(); +//! +//! let usb_bus = UsbBusAllocator::new(UsbBus::new( +//! pac.USBCTRL_REGS, +//! pac.USBCTRL_DPRAM, +//! clocks.usb_clock, +//! true, +//! &mut pac.RESETS, +//! )); +//! // Use the usb_bus as usual. +//! ``` +//! +//! See [pico_usb_serial.rs](https://github.com/rp-rs/rp-hal/tree/main/boards/pico/examples/pico_usb_serial.rs) for more complete examples + +use core::cell::RefCell; + +use crate::clocks::UsbClock; +use crate::pac::RESETS; +use crate::pac::USBCTRL_DPRAM; +use crate::pac::USBCTRL_REGS; +use crate::resets::SubsystemReset; + +use cortex_m::interrupt::{self, Mutex}; + +use usb_device::{ + bus::{PollResult, UsbBus as UsbBusTrait}, + endpoint::{EndpointAddress, EndpointType}, + Result as UsbResult, UsbDirection, UsbError, +}; + +fn ep_addr_to_ep_buf_ctrl_idx(ep_addr: EndpointAddress) -> usize { + ep_addr.index() * 2 + (if ep_addr.is_in() { 0 } else { 1 }) +} +#[derive(Debug)] +struct Endpoint { + ep_type: EndpointType, + max_packet_size: u16, + buffer_offset: u16, +} +impl Endpoint { + unsafe fn get_buf_parts(&self) -> (*mut u8, usize) { + const DPRAM_BASE: *mut u8 = USBCTRL_DPRAM::ptr() as *mut u8; + if self.ep_type == EndpointType::Control { + (DPRAM_BASE.offset(0x100), 64usize) + } else { + ( + DPRAM_BASE.offset(0x180 + (self.buffer_offset * 64) as isize), + self.max_packet_size as usize, + ) + } + } + + fn get_buf(&self) -> &'static [u8] { + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts(base as *const _, len) + } + } + fn get_buf_mut(&self) -> &'static mut [u8] { + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts_mut(base, len) + } + } +} + +struct Inner { + ctrl_reg: USBCTRL_REGS, + ctrl_dpram: USBCTRL_DPRAM, + in_endpoints: [Option; 16], + out_endpoints: [Option; 16], + next_offset: u16, + read_setup: bool, +} +impl Inner { + fn new(ctrl_reg: USBCTRL_REGS, ctrl_dpram: USBCTRL_DPRAM) -> Self { + Self { + ctrl_reg, + ctrl_dpram, + in_endpoints: Default::default(), + out_endpoints: Default::default(), + next_offset: 0, + read_setup: false, + } + } + + fn ep_allocate( + &mut self, + ep_addr: Option, + ep_dir: UsbDirection, + ep_type: EndpointType, + max_packet_size: u16, + ) -> UsbResult { + let ep_addr = ep_addr + .or_else(|| { + let eps = if ep_dir == UsbDirection::In { + self.in_endpoints.iter() + } else { + self.out_endpoints.iter() + }; + // find free end point + let mut iter = eps.enumerate(); + // reserve ep0 for the control endpoint + if ep_type != EndpointType::Control { + iter.next(); + } + iter.find(|(_, ep)| ep.is_none()) + .map(|(index, _)| EndpointAddress::from_parts(index, ep_dir)) + }) + .ok_or(UsbError::EndpointOverflow)?; + + let is_ep0 = ep_addr.index() == 0; + let is_ctrl_ep = ep_type == EndpointType::Control; + if !(is_ep0 ^ !is_ctrl_ep) || (is_ep0 && (max_packet_size != 64)) { + return Err(UsbError::Unsupported); + } + + let eps = if ep_addr.is_in() { + &mut self.in_endpoints + } else { + &mut self.out_endpoints + }; + let maybe_ep = eps + .get_mut(ep_addr.index()) + .ok_or(UsbError::EndpointOverflow)?; + if maybe_ep.is_some() { + return Err(UsbError::InvalidEndpoint); + } + + // validate buffer size + if let (EndpointType::Isochronous, true) = (ep_type, max_packet_size > 1023) { + return Err(UsbError::Unsupported); + } else if max_packet_size > 64 { + return Err(UsbError::Unsupported); + } + + if ep_addr.index() == 0 { + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset: 0, // not used on CTRL ep + }); + } else { + // size in 64bytes units. + // NOTE: the compiler is smart enough to recognize /64 as a 6bit right shift so let's + // keep the division here for the sake of clarity + let aligned_sized = (max_packet_size + 63) / 64; + if (self.next_offset + aligned_sized) > (4096 / 64) { + return Err(UsbError::EndpointMemoryOverflow); + } + + let buffer_offset = self.next_offset; + self.next_offset += aligned_sized; + + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset, + }); + } + Ok(ep_addr) + } + + fn ep_reset_all(&mut self) { + self.ctrl_reg + .sie_ctrl + .modify(|_, w| w.ep0_int_1buf().set_bit()); + // expect ctrl ep to receive on DATA first + self.ctrl_dpram.ep_buffer_control[0].write(|w| w.pid_0().set_bit()); + self.ctrl_dpram.ep_buffer_control[1].write(|w| { + w.available_0().set_bit(); + w.pid_0().set_bit() + }); + + for (index, ep) in itertools::interleave( + self.in_endpoints.iter().skip(1), // skip control endpoint + self.out_endpoints.iter().skip(1), // skip control endpoint + ) + .enumerate() + .filter_map(|(i, ep)| ep.as_ref().map(|ep| (i, ep))) + { + use pac::usbctrl_dpram::ep_control::ENDPOINT_TYPE_A; + let ep_type = match ep.ep_type { + EndpointType::Bulk => ENDPOINT_TYPE_A::BULK, + EndpointType::Isochronous => ENDPOINT_TYPE_A::ISOCHRONOUS, + EndpointType::Control => ENDPOINT_TYPE_A::CONTROL, + EndpointType::Interrupt => ENDPOINT_TYPE_A::INTERRUPT, + }; + // configure + // ep 0 in&out are not part of index (skipped before enumeration) + self.ctrl_dpram.ep_control[index].modify(|_, w| unsafe { + w.endpoint_type().variant(ep_type); + w.interrupt_per_buff().set_bit(); + w.enable().set_bit(); + w.buffer_address().bits(0x180 + (ep.buffer_offset << 6)) + }); + // reset OUT ep and prepare IN ep to accept data + let buf_control = &self.ctrl_dpram.ep_buffer_control[index + 2]; + if (index & 1) == 0 { + // first write occur on DATA0 so prepare the pid bit to be flipped + buf_control.write(|w| w.pid_0().set_bit()); + } else { + buf_control.write(|w| unsafe { + w.available_0().set_bit(); + w.pid_0().clear_bit(); + w.length_0().bits(ep.max_packet_size) + }); + } + } + } + + fn ep_write(&mut self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .in_endpoints + .get_mut(index) + .map(Option::as_mut) + .flatten() + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control[index * 2]; + if buf_control.read().available_0().bit_is_set() { + return Err(UsbError::WouldBlock); + } + + let ep_buf = ep.get_buf_mut(); + if ep_buf.len() < buf.len() { + return Err(UsbError::BufferOverflow); + } + ep_buf[..buf.len()].copy_from_slice(buf); + + buf_control.modify(|r, w| unsafe { + w.available_0().set_bit(); + w.length_0().bits(buf.len() as u16); + w.full_0().set_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + + Ok(buf.len()) + } + + fn ep_read(&mut self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .out_endpoints + .get_mut(index) + .map(Option::as_mut) + .flatten() + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control[index * 2 + 1]; + let buf_control_val = buf_control.read(); + + let process_setup = index == 0 && self.read_setup; + let (ep_buf, len) = if process_setup { + // assume we want to read the setup request + + // Next packet will be on DATA1 so clear pid_0 so it gets flipped by next buf config + self.ctrl_dpram.ep_buffer_control[0].modify(|_, w| w.pid_0().clear_bit()); + // the OUT packet will be either data or a status zlp + // clear setup request flag + self.ctrl_reg.sie_status.write(|w| w.setup_rec().set_bit()); + ( + unsafe { core::slice::from_raw_parts(USBCTRL_DPRAM::ptr() as *const u8, 8) }, + 8, + ) + } else { + if buf_control_val.full_0().bit_is_clear() { + return Err(UsbError::WouldBlock); + } + let len: usize = buf_control_val.length_0().bits().into(); + (ep.get_buf(), len) + }; + + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep_buf[..len]); + + if process_setup { + self.read_setup = false; + + // clear any out standing out flag e.g. in case a zlp got discarded + self.ctrl_reg.buff_status.write(|w| unsafe { w.bits(2) }); + + let data_length = u16::from(buf[6]) | (u16::from(buf[7]) << 8); + let is_in_request = (buf[0] & 0x80) == 0x80; + let expect_data_or_zlp = is_in_request || data_length != 0; + buf_control.modify(|_, w| unsafe { + // enable if and only if a dataphase is expected. + w.available_0().bit(expect_data_or_zlp); + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().set_bit() + }); + } else { + buf_control.modify(|r, w| unsafe { + w.available_0().set_bit(); + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + // Clear OUT flag once it is read. + self.ctrl_reg + .buff_status + .write(|w| unsafe { w.bits(1 << (index * 2 + 1)) }); + } + + Ok(len) + } +} + +/// Usb bus +pub struct UsbBus { + inner: Mutex>, +} + +impl UsbBus { + /// Create new usb bus struct and bring up usb as device. + pub fn new( + ctrl_reg: USBCTRL_REGS, + ctrl_dpram: USBCTRL_DPRAM, + _pll: UsbClock, + force_vbus_detect_bit: bool, + resets: &mut RESETS, + ) -> Self { + ctrl_reg.reset_bring_down(resets); + ctrl_reg.reset_bring_up(resets); + + unsafe { + let raw_ctrl_reg = + core::slice::from_raw_parts_mut(USBCTRL_REGS::ptr() as *mut u32, 1 + 0x98 / 4); + raw_ctrl_reg.fill(0); + + let raw_ctrl_pdram = + core::slice::from_raw_parts_mut(USBCTRL_DPRAM::ptr() as *mut u32, 1 + 0xfc / 4); + raw_ctrl_pdram.fill(0); + } + + ctrl_reg.usb_muxing.modify(|_, w| { + w.to_phy().set_bit(); + w.softcon().set_bit() + }); + + if force_vbus_detect_bit { + ctrl_reg.usb_pwr.modify(|_, w| { + w.vbus_detect().set_bit(); + w.vbus_detect_override_en().set_bit() + }); + } + ctrl_reg.main_ctrl.modify(|_, w| { + w.sim_timing().clear_bit(); + w.host_ndevice().clear_bit(); + w.controller_en().set_bit() + }); + + Self { + inner: Mutex::new(RefCell::new(Inner::new(ctrl_reg, ctrl_dpram))), + } + } +} + +impl UsbBusTrait for UsbBus { + fn alloc_ep( + &mut self, + ep_dir: UsbDirection, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + _interval: u8, + ) -> UsbResult { + interrupt::free(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + inner.ep_allocate(ep_addr, ep_dir, ep_type, max_packet_size) + }) + } + + fn enable(&mut self) { + interrupt::free(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + // at this stage ep's are expected to be in their reset state + // TODO: is it worth having a debug_assert for that here? + + // enable pull up to let the host know we exist. + inner + .ctrl_reg + .sie_ctrl + .modify(|_, w| w.pullup_en().set_bit()); + }) + } + fn reset(&self) { + interrupt::free(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + // clear reset flag + inner.ctrl_reg.sie_status.write(|w| w.bus_reset().set_bit()); + inner + .ctrl_reg + .buff_status + .write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // reset all endpoints + inner.ep_reset_all(); + + // Reset address register + inner.ctrl_reg.addr_endp.reset(); + // TODO: RP2040-E5: work around implementation + // TODO: reset all endpoints & buffer statuses + }) + } + fn set_device_address(&self, addr: u8) { + interrupt::free(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .addr_endp + .modify(|_, w| unsafe { w.address().bits(addr & 0x3F) }); + // reset ep0 + inner.ctrl_dpram.ep_buffer_control[0].modify(|_, w| w.pid_0().set_bit()); + inner.ctrl_dpram.ep_buffer_control[1].modify(|_, w| w.pid_0().set_bit()); + }) + } + fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + interrupt::free(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_write(ep_addr, buf) + }) + } + fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + interrupt::free(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_read(ep_addr, buf) + }) + } + fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { + interrupt::free(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + + if ep_addr.index() == 0 { + inner.ctrl_reg.ep_stall_arm.modify(|_, w| { + if ep_addr.is_in() { + w.ep0_in().bit(stalled) + } else { + w.ep0_out().bit(stalled) + } + }); + } + + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner.ctrl_dpram.ep_buffer_control[index].modify(|_, w| w.stall().bit(stalled)); + }) + } + fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { + interrupt::free(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner.ctrl_dpram.ep_buffer_control[index] + .read() + .stall() + .bit_is_set() + }) + } + fn suspend(&self) { + todo!() + } + fn resume(&self) { + todo!() + } + fn poll(&self) -> PollResult { + interrupt::free(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + // TODO: check for suspend request + // TODO: check for resume request + + // check for bus reset + let sie_status = inner.ctrl_reg.sie_status.read(); + if sie_status.bus_reset().bit_is_set() { + return PollResult::Reset; + } + + let (mut ep_out, mut ep_in_complete, mut ep_setup): (u16, u16, u16) = (0, 0, 0); + + let buff_status = inner.ctrl_reg.buff_status.read().bits(); + if buff_status != 0 { + // IN Complete shall only be reported once. + inner + .ctrl_reg + .buff_status + .write(|w| unsafe { w.bits(0x5555_5555) }); + + for i in 0..32u32 { + let mask = 1 << i; + if (buff_status & mask) == mask { + if (i & 1) == 0 { + ep_in_complete |= 1 << (i / 2); + } else { + ep_out |= 1 << (i / 2); + } + } + } + } + // check for setup request + // Only report setup if OUT has been cleared. + if sie_status.setup_rec().bit_is_set() { + ep_setup |= 1; + inner.read_setup = true; + } + + if let (0, 0, 0) = (ep_out, ep_in_complete, ep_setup) { + return PollResult::None; + } + PollResult::Data { + ep_out, + ep_in_complete, + ep_setup, + } + }) + } + + const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; +}