Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
247 changes: 167 additions & 80 deletions src/endpoint_memory.rs
Original file line number Diff line number Diff line change
@@ -1,140 +1,230 @@
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,
}
}

#[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);
}
fn get_mem_slice<T>(&self) -> &mut [VolatileCell<T>] {
Copy link
Member

@Disasm Disasm Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this function is very unsafe because calling it twice within the same block is unsound.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. I realized that I can return &[VolatileCell<T>], which relaxes the safety requirements quite a bit. I did also mark it as unsafe for posterity.

unsafe { slice::from_raw_parts_mut(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 = self.get_mem_slice::<u32>();

while buf.len() >= 2 {
let word = self.read_word(index);
let mut index = 0;

buf[0] = (word & 0xff) as u8;
buf[1] = (word >> 8) as u8;
while buf.len() >= 4 {
let value = mem[index].get().to_ne_bytes();
index += USB::EP_MEMORY_ACCESS.offset_multiplier();

index += 1;
buf[0..4].copy_from_slice(&value);
buf = &mut buf[4..];
}

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

let mut index = 0;

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

if buf.len() > 0 {
buf[0] = (self.read_word(index) & 0xff) as u8;
buf[0..2].copy_from_slice(&value);
buf = &mut buf[2..];
}

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 = self.get_mem_slice::<u32>();

let mut index = 0;

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();
}

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

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

if buf.len() > 0 {
self.write_word(index, buf[0] as u16);
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 {
self.mem.len() / 2
};
len_words << 1
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 {
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 +257,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