Skip to content

Implement support for 32-bit memory access scheme #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/hal.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! USB peripheral

use stm32_usbd::UsbPeripheral;
use stm32_usbd::{MemoryAccess, UsbPeripheral};
use stm32f1xx_hal::pac::{RCC, USB};

pub use stm32_usbd::UsbBus;
Expand All @@ -20,7 +20,7 @@ unsafe impl UsbPeripheral for Peripheral {
const DP_PULL_UP_FEATURE: bool = false;
const EP_MEMORY: *const () = 0x4000_6000 as _;
const EP_MEMORY_SIZE: usize = 512;
const EP_MEMORY_ACCESS_2X16: bool = false;
const EP_MEMORY_ACCESS: MemoryAccess = MemoryAccess::Word16x1;

fn enable() {
let rcc = unsafe { &*RCC::ptr() };
Expand Down
250 changes: 170 additions & 80 deletions src/endpoint_memory.rs
Original file line number Diff line number Diff line change
@@ -1,140 +1,233 @@
use crate::endpoint::NUM_ENDPOINTS;
use crate::UsbPeripheral;
use core::marker::PhantomData;
use core::slice;
use core::{marker::PhantomData, mem};
use usb_device::{Result, UsbError};
use vcell::VolatileCell;

/// Different endpoint memory access schemes
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum MemoryAccess {
/// 16x1 bits per word. Each 32-bit word is accessed as one 16-bit half-word. The second half-word of the word is ignored.
///
/// This matches the behavior of `EP_MEMORY_ACCESS_2X16 = false` from previous versions of this library.
Word16x1,
/// 16x2 bits per word. Each 32-bit word is accessed as two 16-bit half-words.
///
/// This matches the behavior of `EP_MEMORY_ACCESS_2X16 = true` from previous versions of this library.
Word16x2,
/// 32x1 bits per word. Each 32-bit word is accessed as one 32-bit word.
Word32x1,
}

impl MemoryAccess {
/// Value to multiply offsets within the EP memory by when calculating address to read or write to.
#[inline(always)]
const fn offset_multiplier(self) -> usize {
match self {
MemoryAccess::Word16x1 => 2,
MemoryAccess::Word16x2 | MemoryAccess::Word32x1 => 1,
}
}

/// Size of unit used when reading and writing EP memory, in bytes.
#[inline(always)]
const fn unit_size(self) -> usize {
match self {
MemoryAccess::Word16x1 | MemoryAccess::Word16x2 => 2,
MemoryAccess::Word32x1 => 4,
}
}
}

pub struct EndpointBuffer<USB> {
mem: &'static mut [VolatileCell<u16>],
mem_ptr: *mut (),
mem_len: usize,
marker: PhantomData<USB>,
}

unsafe impl<USB> Send for EndpointBuffer<USB> {}

impl<USB: UsbPeripheral> EndpointBuffer<USB> {
pub fn new(offset_bytes: usize, size_bytes: usize) -> Self {
let ep_mem_ptr = USB::EP_MEMORY as *mut VolatileCell<u16>;

let offset_words = offset_bytes >> 1;
let count_words = size_bytes >> 1;
let offset_u16_words;
let count_u16_words;
if USB::EP_MEMORY_ACCESS_2X16 {
offset_u16_words = offset_words;
count_u16_words = count_words;
} else {
offset_u16_words = offset_words * 2;
count_u16_words = count_words * 2;
};

unsafe {
let mem = slice::from_raw_parts_mut(ep_mem_ptr.add(offset_u16_words), count_u16_words);
Self {
mem,
marker: PhantomData,
}
}
}
let mem_offset_bytes = offset_bytes * USB::EP_MEMORY_ACCESS.offset_multiplier();
let mem_len = size_bytes * USB::EP_MEMORY_ACCESS.offset_multiplier() / USB::EP_MEMORY_ACCESS.unit_size();

#[inline(always)]
fn read_word(&self, index: usize) -> u16 {
if USB::EP_MEMORY_ACCESS_2X16 {
self.mem[index].get()
} else {
self.mem[index * 2].get()
let mem_ptr = unsafe { USB::EP_MEMORY.byte_add(mem_offset_bytes).cast_mut() };
Self {
mem_ptr,
mem_len,
marker: PhantomData,
}
}

/// # Safety
///
/// Caller must ensure that while the returned reference exists, no mutable references to the section of EP memory covered by this slice exist.
#[inline(always)]
fn write_word(&self, index: usize, value: u16) {
if USB::EP_MEMORY_ACCESS_2X16 {
self.mem[index].set(value);
} else {
self.mem[index * 2].set(value);
}
unsafe fn get_mem_slice<T>(&self) -> &[VolatileCell<T>] {
unsafe { slice::from_raw_parts(self.mem_ptr.cast(), self.mem_len) }
}

pub fn read(&self, mut buf: &mut [u8]) {
let mut index = 0;
if USB::EP_MEMORY_ACCESS == MemoryAccess::Word32x1 {
let mem = unsafe { self.get_mem_slice::<u32>() };

let mut index = 0;

while buf.len() >= 2 {
let word = self.read_word(index);
while buf.len() >= 4 {
let value = mem[index].get().to_ne_bytes();
index += USB::EP_MEMORY_ACCESS.offset_multiplier();

buf[0] = (word & 0xff) as u8;
buf[1] = (word >> 8) as u8;
buf[0..4].copy_from_slice(&value);
buf = &mut buf[4..];
}

index += 1;
if buf.len() > 0 {
let value = mem[index].get().to_ne_bytes();
buf.copy_from_slice(&value[0..buf.len()]);
}
} else {
let mem = unsafe { self.get_mem_slice::<u16>() };

buf = &mut buf[2..];
}
let mut index = 0;

while buf.len() >= 2 {
let value = mem[index].get().to_ne_bytes();
index += USB::EP_MEMORY_ACCESS.offset_multiplier();

buf[0..2].copy_from_slice(&value);
buf = &mut buf[2..];
}

if buf.len() > 0 {
buf[0] = (self.read_word(index) & 0xff) as u8;
if buf.len() > 0 {
let value = mem[index].get().to_ne_bytes();
buf.copy_from_slice(&value[0..buf.len()]);
}
}
}

pub fn write(&self, mut buf: &[u8]) {
let mut index = 0;
if USB::EP_MEMORY_ACCESS == MemoryAccess::Word32x1 {
let mem = unsafe { self.get_mem_slice::<u32>() };

while buf.len() >= 2 {
let value: u16 = buf[0] as u16 | ((buf[1] as u16) << 8);
self.write_word(index, value);
index += 1;
let mut index = 0;

buf = &buf[2..];
}
while buf.len() >= 4 {
let mut value = [0; 4];
value.copy_from_slice(&buf[0..4]);
buf = &buf[4..];

mem[index].set(u32::from_ne_bytes(value));
index += USB::EP_MEMORY_ACCESS.offset_multiplier();
}

if buf.len() > 0 {
let mut value = [0; 4];
value[0..buf.len()].copy_from_slice(buf);
mem[index].set(u32::from_ne_bytes(value));
}
} else {
let mem = unsafe { self.get_mem_slice::<u16>() };

if buf.len() > 0 {
self.write_word(index, buf[0] as u16);
let mut index = 0;

while buf.len() >= 2 {
let mut value = [0; 2];
value.copy_from_slice(&buf[0..2]);
buf = &buf[2..];

mem[index].set(u16::from_ne_bytes(value));
index += USB::EP_MEMORY_ACCESS.offset_multiplier();
}

if buf.len() > 0 {
let mut value = [0; 2];
value[0..buf.len()].copy_from_slice(buf);
mem[index].set(u16::from_ne_bytes(value));
}
}
}

pub fn offset(&self) -> u16 {
let buffer_address = self.mem.as_ptr() as usize;
let word_size = if USB::EP_MEMORY_ACCESS_2X16 { 2 } else { 4 };
let index = (buffer_address - USB::EP_MEMORY as usize) / word_size;
(index << 1) as u16
let offset_bytes = self.mem_ptr as usize - USB::EP_MEMORY as usize;
(offset_bytes / USB::EP_MEMORY_ACCESS.offset_multiplier()) as u16
}

pub fn capacity(&self) -> usize {
let len_words = if USB::EP_MEMORY_ACCESS_2X16 {
self.mem.len()
self.mem_len * USB::EP_MEMORY_ACCESS.unit_size() / USB::EP_MEMORY_ACCESS.offset_multiplier()
}
}

pub struct Field<USB> {
ptr: *const (),
marker: PhantomData<USB>,
}

impl<USB: UsbPeripheral> Field<USB> {
#[inline(always)]
pub fn get(&self) -> u16 {
if USB::EP_MEMORY_ACCESS == MemoryAccess::Word32x1 {
let unaligned_offset = self.ptr as usize & 0b11;
let cell: &VolatileCell<u32> = unsafe { &*self.ptr.byte_sub(unaligned_offset).cast() };
let value: [u16; 2] = unsafe { mem::transmute(cell.get()) };
value[unaligned_offset >> 1]
} else {
let cell: &VolatileCell<u16> = unsafe { &*self.ptr.cast() };
cell.get()
}
}

#[inline(always)]
pub fn set(&self, value: u16) {
if USB::EP_MEMORY_ACCESS == MemoryAccess::Word32x1 {
let unaligned_offset = self.ptr as usize & 0b11;
let cell: &VolatileCell<u32> = unsafe { &*self.ptr.byte_sub(unaligned_offset).cast() };
let mut previous_value: [u16; 2] = unsafe { mem::transmute(cell.get()) };
previous_value[unaligned_offset >> 1] = value;
cell.set(unsafe { mem::transmute(previous_value) });
} else {
self.mem.len() / 2
};
len_words << 1
let cell: &VolatileCell<u16> = unsafe { &*self.ptr.cast() };
cell.set(value);
}
}
}

#[repr(C)]
pub struct BufferDescriptor<USB> {
ptr: *const VolatileCell<u16>,
ptr: *const (),
marker: PhantomData<USB>,
}

impl<USB: UsbPeripheral> BufferDescriptor<USB> {
#[inline(always)]
fn field(&self, index: usize) -> &'static VolatileCell<u16> {
let mul = if USB::EP_MEMORY_ACCESS_2X16 { 1 } else { 2 };
unsafe { &*(self.ptr.add(index * mul)) }
fn field(&self, index: usize) -> Field<USB> {
let mul = USB::EP_MEMORY_ACCESS.offset_multiplier();
let ptr = unsafe { self.ptr.byte_add(index * 2 * mul) };
Field {
ptr,
marker: PhantomData,
}
}

#[inline(always)]
pub fn addr_tx(&self) -> &'static VolatileCell<u16> {
pub fn addr_tx(&self) -> Field<USB> {
self.field(0)
}

#[inline(always)]
pub fn count_tx(&self) -> &'static VolatileCell<u16> {
pub fn count_tx(&self) -> Field<USB> {
self.field(1)
}

#[inline(always)]
pub fn addr_rx(&self) -> &'static VolatileCell<u16> {
pub fn addr_rx(&self) -> Field<USB> {
self.field(2)
}

#[inline(always)]
pub fn count_rx(&self) -> &'static VolatileCell<u16> {
pub fn count_rx(&self) -> Field<USB> {
self.field(3)
}
}
Expand Down Expand Up @@ -167,14 +260,11 @@ impl<USB: UsbPeripheral> EndpointMemoryAllocator<USB> {
}

pub fn buffer_descriptor(index: u8) -> BufferDescriptor<USB> {
let mul = if USB::EP_MEMORY_ACCESS_2X16 { 1 } else { 2 };

unsafe {
let ptr = (USB::EP_MEMORY as *const VolatileCell<u16>).add((index as usize) * 4 * mul);
BufferDescriptor {
ptr,
marker: Default::default(),
}
let mul = USB::EP_MEMORY_ACCESS.offset_multiplier();
let ptr = unsafe { USB::EP_MEMORY.byte_add((index as usize) * 8 * mul).cast() };
BufferDescriptor {
ptr,
marker: Default::default(),
}
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod endpoint;
mod endpoint_memory;
mod registers;
pub use crate::bus::UsbBus;
pub use crate::endpoint_memory::MemoryAccess;

mod pac;

Expand All @@ -31,9 +32,8 @@ pub unsafe trait UsbPeripheral: Send + Sync {

/// Endpoint memory access scheme
///
/// Check Reference Manual for details.
/// Set to `true` if "2x16 bits/word" access scheme is used, otherwise set to `false`.
const EP_MEMORY_ACCESS_2X16: bool;
/// See `MemoryAccess` enum for more details. Check Reference Manual to determine the correct access scheme to use.
const EP_MEMORY_ACCESS: MemoryAccess;

/// Enables USB device on its peripheral bus
fn enable();
Expand Down
Loading