feat(firmware): validate configuration and migrate sensor-driven flight APIs

Validate configuration through its owning schemas and keep board
declarations separate from shared F722 resource ownership. Bound USB
transitions, isolate transport failures and retain restart-only activation.

Migrate navigation and flight consumers with the sensor and schema
contracts. Require explicit installation data and synchronous output
authority; board motor outputs remain inhibited.

Validation: assembled-prefix host tests, strict Clippy, platform/USB tests
and both ARM release builds. Final-source coverage and scoped Miri/Kani
evidence are retained separately from hardware qualification.
This commit is contained in:
Onur Özkan committed 2026-11-07 18:12:11 +03:00
1 parent fb1b8af2d1
commit f9a61a086f
211 files changed
+45657 -12740

No files matched your search

+941
View File
@@ -0,0 +1,941 @@
use crate::ral::{
modify_reg, otg_device, otg_global, otg_global_dieptxfx, otg_pwrclk, read_reg, write_reg,
};
use crate::transition::{EndpointConfig, EndpointDescriptor};
use core::{cell::Cell, marker::PhantomData};
use embedded_hal::blocking::delay::DelayMs;
use usb_device::bus::{PollResult, UsbBusAllocator};
use usb_device::endpoint::{EndpointAddress, EndpointType};
use usb_device::{Result, UsbDirection, UsbError};
use crate::endpoint::{EndpointIn, EndpointOut};
use crate::endpoint_memory::{EndpointBufferState, EndpointMemoryAllocator};
use crate::target::UsbRegisters;
use crate::{PhyType, UsbPeripheral};
use critical_section::Mutex;
/// USB peripheral driver for STM32 microcontrollers.
pub struct UsbBus<USB> {
peripheral: USB,
regs: UsbRegisters,
state: Mutex<Cell<BusState>>,
allocator: EndpointAllocator<USB>,
}
#[derive(Clone, Copy, PartialEq)]
enum BusState {
Ready,
Busy,
FaultPending,
Offline,
}
// This private guard never leaves access(). The short state critical sections
// exclude other USB callers. Hardware waits hold only this operation ownership.
struct Operation<'a, USB: UsbPeripheral> {
bus: &'a UsbBus<USB>,
restore_interrupt: bool,
active: bool,
}
impl<USB: UsbPeripheral> Operation<'_, USB> {
fn finish(mut self) -> bool {
let healthy = critical_section::with(|cs| {
let state = self.bus.state.borrow(cs);
if state.get() == BusState::Busy {
modify_reg!(otg_global, self.bus.regs.global(), GAHBCFG,
GINT: self.restore_interrupt as u32);
// Complete the mask write before another caller takes ownership.
let _ = read_reg!(otg_global, self.bus.regs.global(), GAHBCFG);
state.set(BusState::Ready);
true
} else {
false
}
});
if !healthy {
self.bus.abort();
}
self.active = false;
healthy
}
}
impl<USB: UsbPeripheral> Drop for Operation<'_, USB> {
fn drop(&mut self) {
if self.active {
// An unwound operation may have changed hardware only partially.
self.bus.disable();
self.bus.abort();
}
}
}
impl<USB: UsbPeripheral> UsbBus<USB> {
/// Constructs a new USB peripheral driver.
pub fn new(peripheral: USB, ep_memory: &'static mut [u32]) -> UsbBusAllocator<Self> {
let bus = UsbBus {
peripheral,
regs: UsbRegisters::new::<USB>(),
state: Mutex::new(Cell::new(BusState::Ready)),
allocator: EndpointAllocator::new(ep_memory),
};
UsbBusAllocator::new(bus)
}
pub fn free(self) -> USB {
self.peripheral
}
/// Reports a terminal fault, including an abort awaiting the active owner.
pub fn is_offline(&self) -> bool {
critical_section::with(|cs| {
matches!(
self.state.borrow(cs).get(),
BusState::FaultPending | BusState::Offline
)
})
}
/// Permanently disables USB. Only reconstruction after reset can reconnect it.
pub fn disable(&self) {
let abort = critical_section::with(|cs| {
let state = self.state.borrow(cs);
match state.get() {
BusState::Ready => {
state.set(BusState::FaultPending);
true
}
BusState::Busy => {
state.set(BusState::FaultPending);
false
}
BusState::FaultPending | BusState::Offline => false,
}
});
if abort {
self.abort();
}
}
fn abort(&self) {
// No USB MMIO is allowed while a reset or flush might still be active.
self.peripheral.abort();
critical_section::with(|cs| self.state.borrow(cs).set(BusState::Offline));
}
fn access<R>(&self, f: impl FnOnce(&mut Operation<'_, USB>) -> R) -> Result<R> {
let restore_interrupt = critical_section::with(|cs| {
let state = self.state.borrow(cs);
match state.get() {
BusState::Ready => {
state.set(BusState::Busy);
let enabled = read_reg!(otg_global, self.regs.global(), GAHBCFG, GINT) != 0;
modify_reg!(otg_global, self.regs.global(), GAHBCFG, GINT: 0);
// Read back this same peripheral before releasing exclusion.
let _ = read_reg!(otg_global, self.regs.global(), GAHBCFG);
Ok(enabled)
}
BusState::Busy => Err(UsbError::WouldBlock),
BusState::FaultPending | BusState::Offline => Err(UsbError::InvalidState),
}
})?;
let mut operation = Operation {
bus: self,
restore_interrupt,
active: true,
};
let result = f(&mut operation);
if operation.finish() {
Ok(result)
} else {
Err(UsbError::InvalidState)
}
}
fn mutate(&self, f: impl FnOnce(&mut Operation<'_, USB>)) {
if self.access(f) == Err(UsbError::WouldBlock) {
// These trait methods cannot return a retry. A discarded reset or
// address change would leave the USB device and hardware incoherent.
self.disable();
}
}
fn wait(&self, ready: impl Fn() -> bool) -> bool {
let start = self.peripheral.now_us();
for _ in 0..USB::WAIT_POLL_LIMIT {
let now = self.peripheral.now_us();
if self.is_offline() || now < start || now - start >= u64::from(USB::WAIT_TIMEOUT_US) {
break;
}
if ready() {
return true;
}
}
self.disable();
false
}
fn configure_all(&self) -> bool {
let regs = &self.regs;
// Rx FIFO
// This calculation doesn't correspond to one in a Reference Manual.
// In fact, the required number of words is higher than indicated in RM.
// The following numbers are pessimistic and were figured out empirically.
let rx_fifo_size = if USB::HIGH_SPEED {
self.allocator.memory_allocator.total_rx_buffer_size_words() + 30
} else {
// F429 requires 35+ words for the (EP0[8] + EP2[64]) setup
// F446 requires 39+ words for the same setup
self.allocator.memory_allocator.total_rx_buffer_size_words() + 30
};
write_reg!(otg_global, regs.global(), GRXFSIZ, rx_fifo_size as u32);
let mut fifo_top = rx_fifo_size;
// Tx FIFO #0
let fifo_size = self.allocator.memory_allocator.tx_fifo_size_words(0);
#[cfg(feature = "fs")]
write_reg!(otg_global, regs.global(), DIEPTXF0,
TX0FD: fifo_size as u32,
TX0FSA: fifo_top as u32
);
#[cfg(feature = "hs")]
write_reg!(otg_global, regs.global(), GNPTXFSIZ,
TX0FD: fifo_size as u32,
TX0FSA: fifo_top as u32
);
fifo_top += fifo_size;
// Tx FIFOs
for i in 1..USB::ENDPOINT_COUNT {
let fifo_size = self.allocator.memory_allocator.tx_fifo_size_words(i);
let dieptxfx = regs.dieptxfx(i);
write_reg!(otg_global_dieptxfx, dieptxfx, DIEPTXFx,
INEPTXFD: fifo_size as u32,
INEPTXSA: fifo_top as u32
);
fifo_top += fifo_size;
}
assert!(fifo_top as usize <= USB::FIFO_DEPTH_WORDS);
// Flush Rx & Tx FIFOs
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) != 0) {
return false;
}
modify_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH: 1, TXFFLSH: 1, TXFNUM: 0x10);
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH, TXFFLSH) == (0, 0))
{
return false;
}
for ep in &self.allocator.endpoints_in {
if let Some(ep) = ep {
// enabling EP TX interrupt
modify_reg!(otg_device, regs.device(), DAINTMSK, |v| v
| (0x0001 << ep.address().index()));
ep.configure();
}
}
for ep in &self.allocator.endpoints_out {
if let Some(ep) = ep {
if ep.address().index() == 0 {
// enabling RX interrupt from EP0
modify_reg!(otg_device, regs.device(), DAINTMSK, |v| v | 0x00010000);
}
ep.configure();
}
}
true
}
fn deconfigure_all(&self) {
let regs = &self.regs;
// disable interrupts
modify_reg!(otg_device, regs.device(), DAINTMSK, IEPM: 0, OEPM: 0);
for ep in &self.allocator.endpoints_in {
if let Some(ep) = ep {
ep.deconfigure();
}
}
for ep in &self.allocator.endpoints_out {
if let Some(ep) = ep {
ep.deconfigure();
}
}
}
pub fn force_reset(&self, delay: &mut impl DelayMs<u32>) -> Result<()> {
self.access(|_| {
let regs = &self.regs;
write_reg!(otg_device, regs.device(), DCTL, SDIS: 1); // Soft disconnect
delay.delay_ms(3);
if self.is_offline() {
return;
}
write_reg!(otg_device, regs.device(), DCTL, SDIS: 0); // Soft connect
delay.delay_ms(3);
})
}
#[cfg(feature = "hs")]
/// Reads from a ULPI register in an external ULPI PHY.
///
/// Other USB operations are excluded for the duration of this call.
///
/// **Panics:** if `phy_type` is not `PhyType::ExternalHighSpeed`.
///
/// # Example
///
/// ```
/// # use synopsys_usb_otg::{UsbPeripheral, UsbBus};
/// fn read_usb_vid_pid<USB: UsbPeripheral>(bus: &UsbBus<USB>) -> (u16, u16) {
/// let mut vid: u16 = bus.ulpi_read(0x00) as u16;
/// vid |= (bus.ulpi_read(0x01) as u16) << 8;
/// let mut pid: u16 = bus.ulpi_read(0x02) as u16;
/// pid |= (bus.ulpi_read(0x03) as u16) << 8;
/// (vid, pid)
/// }
/// ```
pub fn ulpi_read(&self, addr: u8) -> core::result::Result<u8, UlpiError> {
if self.peripheral.phy_type() != PhyType::ExternalHighSpeed {
panic!("ulpi_read is only supported with external ULPI PHYs");
}
self.access(|_| {
let regs = &self.regs;
// Begin ULPI register read transaction
modify_reg!(otg_global, regs.global(), PHYCR,
NEW: 1,
RW: 0,
ADDR: addr as u32
);
// ULPI transactions should take less than 1us. 1000 iterations should be enough even for fast MCUs.
let mut timeout = 1000;
while timeout > 0 {
// Wait for transaction to complete
if read_reg!(otg_global, regs.global(), PHYCR, DONE) != 0 {
// Read transaction data
return Ok((read_reg!(otg_global, regs.global(), PHYCR, DATA) & 0xFF) as u8);
}
timeout -= 1;
}
Err(UlpiError::Timeout)
})
.map_err(|_| UlpiError::Timeout)?
}
#[cfg(feature = "hs")]
/// Writes to a ULPI register in an external ULPI PHY.
///
/// Other USB operations are excluded for the duration of this call.
///
/// **Panics:** if `phy_type` is not `PhyType::ExternalHighSpeed`.
pub fn ulpi_write(&self, addr: u8, data: u8) -> core::result::Result<(), UlpiError> {
if self.peripheral.phy_type() != PhyType::ExternalHighSpeed {
panic!("ulpi_write is only supported with external ULPI PHYs");
}
self.access(|_| {
let regs = &self.regs;
// Begin ULPI register write transaction
modify_reg!(otg_global, regs.global(), PHYCR,
NEW: 1,
RW: 1,
ADDR: addr as u32,
DATA: data as u32
);
// ULPI transactions should take less than 1us. 1000 iterations should be enough even for fast MCUs.
let mut timeout = 1000;
while timeout > 0 {
// Wait for transaction to complete
if read_reg!(otg_global, regs.global(), PHYCR, DONE) != 0 {
return Ok(());
}
timeout -= 1;
}
Err(UlpiError::Timeout)
})
.map_err(|_| UlpiError::Timeout)?
}
}
#[cfg(feature = "hs")]
#[derive(Debug)]
/// Errors that can occur while interfacing with a ULPI PHY.
pub enum UlpiError {
/// The action has timed out.
Timeout,
}
pub(crate) struct EndpointAllocator<USB> {
bitmap_in: u8,
bitmap_out: u8,
endpoints_in: [Option<EndpointIn>; 9],
endpoints_out: [Option<EndpointOut>; 9],
memory_allocator: EndpointMemoryAllocator<USB>,
_marker: PhantomData<USB>,
}
impl<USB: UsbPeripheral> EndpointAllocator<USB> {
fn new(memory: &'static mut [u32]) -> Self {
assert!(USB::ENDPOINT_COUNT <= 9);
Self {
bitmap_in: 0,
bitmap_out: 0,
// [None; 9] requires Copy
endpoints_in: [None, None, None, None, None, None, None, None, None],
endpoints_out: [None, None, None, None, None, None, None, None, None],
memory_allocator: EndpointMemoryAllocator::new(memory),
_marker: PhantomData,
}
}
fn alloc_number(bitmap: &mut u8, number: Option<u8>) -> Result<u8> {
if let Some(number) = number {
if number as usize >= USB::ENDPOINT_COUNT {
return Err(UsbError::InvalidEndpoint);
}
if *bitmap & (1 << number) == 0 {
*bitmap |= 1 << number;
Ok(number)
} else {
Err(UsbError::InvalidEndpoint)
}
} else {
// Skip EP0
for number in 1..USB::ENDPOINT_COUNT {
if *bitmap & (1 << number) == 0 {
*bitmap |= 1 << number;
return Ok(number as u8);
}
}
Err(UsbError::EndpointOverflow)
}
}
fn alloc(
bitmap: &mut u8,
config: &EndpointConfig,
direction: UsbDirection,
) -> Result<EndpointDescriptor> {
let number = Self::alloc_number(bitmap, config.number)?;
let address = EndpointAddress::from_parts(number as usize, direction);
Ok(EndpointDescriptor {
address,
ep_type: config.ep_type,
max_packet_size: config.max_packet_size,
interval: config.interval,
})
}
fn alloc_in(&mut self, config: &EndpointConfig) -> Result<EndpointIn> {
let descr = Self::alloc(&mut self.bitmap_in, config, UsbDirection::In)?;
self.memory_allocator
.allocate_tx_buffer(descr.address.index() as u8, descr.max_packet_size as usize)?;
let ep = EndpointIn::new::<USB>(descr);
Ok(ep)
}
fn alloc_out(&mut self, config: &EndpointConfig) -> Result<EndpointOut> {
let descr = Self::alloc(&mut self.bitmap_out, config, UsbDirection::Out)?;
let buffer = self
.memory_allocator
.allocate_rx_buffer(descr.max_packet_size as usize)?;
let ep = EndpointOut::new::<USB>(descr, buffer);
Ok(ep)
}
fn alloc_ep(
&mut self,
ep_dir: UsbDirection,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<EndpointAddress> {
let ep_type = unsafe { core::mem::transmute(ep_type) };
let number = ep_addr.map(|a| a.index() as u8);
let config = EndpointConfig {
ep_type,
max_packet_size,
interval,
number,
pair_of: None,
};
match ep_dir {
UsbDirection::Out => {
let ep = self.alloc_out(&config)?;
let address = ep.address();
self.endpoints_out[address.index()] = Some(ep);
Ok(address)
}
UsbDirection::In => {
let ep = self.alloc_in(&config)?;
let address = ep.address();
self.endpoints_in[address.index()] = Some(ep);
Ok(address)
}
}
}
}
impl<USB: UsbPeripheral> usb_device::bus::UsbBus for UsbBus<USB> {
fn alloc_ep(
&mut self,
ep_dir: UsbDirection,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<EndpointAddress> {
self.allocator
.alloc_ep(ep_dir, ep_addr, ep_type, max_packet_size, interval)
}
fn enable(&mut self) {
if self.is_offline() {
return;
}
// Enable USB_OTG in RCC
USB::enable();
let _ = self.access(|operation| {
let regs = &self.regs;
let core_id = read_reg!(otg_global, regs.global(), CID);
// Wait for AHB ready
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) != 0) {
return;
}
// Configure OTG as device
#[cfg(feature = "fs")]
modify_reg!(otg_global, regs.global(), GUSBCFG,
SRPCAP: 0, // SRP capability is not enabled
FDMOD: 1 // Force device mode
);
#[cfg(feature = "hs")]
modify_reg!(otg_global, regs.global(), GUSBCFG,
SRPCAP: 0, // SRP capability is not enabled
TOCAL: 0x1,
FDMOD: 1 // Force device mode
);
// Configure USB PHY
#[cfg(feature = "hs")]
match self.peripheral.phy_type() {
PhyType::InternalFullSpeed => {
// Select FS Embedded PHY
modify_reg!(otg_global, regs.global(), GUSBCFG, PHYSEL: 1);
}
PhyType::InternalHighSpeed => {
// Turn off PHY
modify_reg!(otg_global, regs.global(), GCCFG, PWRDWN: 0);
// Init The UTMI Interface
modify_reg!(otg_global, regs.global(), GUSBCFG,
TSDPS: 0,
ULPIFSLS: 0,
PHYSEL: 0 // ULPI or UTMI
);
// Select VBUS source
modify_reg!(otg_global, regs.global(), GUSBCFG,
ULPIEVBUSD: 0,
ULPIEVBUSI: 0
);
// Select UTMI Interace
//modify_reg!(otg_global, regs.global(), GUSBCFG, ULPISEL: 0);
modify_reg!(otg_global, regs.global(), GUSBCFG, |r| r & !(1 << 4));
// This is a secret bit from ST that is not mentioned anywhere except
// the driver code shipped with STM32CubeIDE.
//modify_reg!(otg_global, regs.global(), GCCFG, PHYHSEN: 1);
modify_reg!(otg_global, regs.global(), GCCFG, |r| r | (1 << 23));
self.peripheral.setup_internal_hs_phy();
}
PhyType::ExternalHighSpeed => {
// Turn off embedded PHY
modify_reg!(otg_global, regs.global(), GCCFG, PWRDWN: 0);
// Init The ULPI Interface
modify_reg!(otg_global, regs.global(), GUSBCFG,
TSDPS: 0,
ULPIFSLS: 0,
PHYSEL: 0 // ULPI or UTMI
);
// Select VBUS source
modify_reg!(otg_global, regs.global(), GUSBCFG,
ULPIEVBUSD: 0,
ULPIEVBUSI: 0
);
}
}
// Perform core soft-reset
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) != 0) {
return;
}
modify_reg!(otg_global, regs.global(), GRSTCTL, CSRST: 1);
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, CSRST) == 0) {
return;
}
if self.peripheral.phy_type() == PhyType::InternalFullSpeed {
// Activate the USB Transceiver
modify_reg!(otg_global, regs.global(), GCCFG, PWRDWN: 1);
}
// Configuring Vbus sense and SOF output
match core_id {
0x0000_1200 | 0x0000_1100 => {
// F429-like chips have the GCCFG.NOVBUSSENS bit
//modify_reg!(otg_global, regs.global, GCCFG, NOVBUSSENS: 1);
modify_reg!(otg_global, regs.global(), GCCFG, |r| r | (1 << 21));
modify_reg!(otg_global, regs.global(), GCCFG, VBUSASEN: 0, VBUSBSEN: 0, SOFOUTEN: 0);
}
0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => {
// F446-like chips have the GCCFG.VBDEN bit with the opposite meaning
//modify_reg!(otg_global, regs.global, GCCFG, VBDEN: 0);
modify_reg!(otg_global, regs.global(), GCCFG, |r| r & !(1 << 21));
// Force B-peripheral session
//modify_reg!(otg_global, regs.global, GOTGCTL, BVALOEN: 1, BVALOVAL: 1);
modify_reg!(otg_global, regs.global(), GOTGCTL, |r| r | (0b11 << 6));
}
_ => {}
}
// Enable PHY clock
write_reg!(otg_pwrclk, regs.pwrclk(), PCGCCTL, 0);
// Soft disconnect device
modify_reg!(otg_device, regs.device(), DCTL, SDIS: 1);
// Setup USB speed and frame interval
let speed = match (USB::HIGH_SPEED, self.peripheral.phy_type()) {
(false, _) => 0b11,
(true, PhyType::InternalFullSpeed) => 0b11,
(true, PhyType::InternalHighSpeed) => 0b00,
(true, PhyType::ExternalHighSpeed) => 0b00,
};
modify_reg!(otg_device, regs.device(), DCFG,
PFIVL: 0b00,
DSPD: speed
);
#[cfg(feature = "xcvrdly")]
modify_reg!(otg_device, regs.device(), DCFG, XCVRDLY: 1);
// unmask EP interrupts
write_reg!(otg_device, regs.device(), DIEPMSK, XFRCM: 1);
// unmask core interrupts
write_reg!(otg_global, regs.global(), GINTMSK,
USBRST: 1, ENUMDNEM: 1,
USBSUSPM: 1, WUIM: 1,
IEPINT: 1, RXFLVLM: 1
);
// clear pending interrupts
write_reg!(otg_global, regs.global(), GINTSTS, 0xffffffff);
// Publish USB interrupts only when the operation finishes healthy.
operation.restore_interrupt = true;
// connect(true)
modify_reg!(otg_device, regs.device(), DCTL, SDIS: 0);
});
}
fn reset(&self) {
self.mutate(|_| {
if self.configure_all() {
modify_reg!(otg_device, self.regs.device(), DCFG, DAD: 0);
}
});
}
fn set_device_address(&self, addr: u8) {
self.mutate(|_| {
modify_reg!(otg_device, self.regs.device(), DCFG, DAD: addr as u32);
});
}
fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> Result<usize> {
if !ep_addr.is_in() || ep_addr.index() >= USB::ENDPOINT_COUNT {
return Err(UsbError::InvalidEndpoint);
}
self.access(|_| {
if let Some(ep) = &self.allocator.endpoints_in[ep_addr.index()] {
ep.write(buf).map(|_| buf.len())
} else {
Err(UsbError::InvalidEndpoint)
}
})?
}
fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> Result<usize> {
if !ep_addr.is_out() || ep_addr.index() >= USB::ENDPOINT_COUNT {
return Err(UsbError::InvalidEndpoint);
}
self.access(|_| {
if let Some(ep) = &self.allocator.endpoints_out[ep_addr.index()] {
ep.read(buf)
} else {
Err(UsbError::InvalidEndpoint)
}
})?
}
fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) {
if ep_addr.index() >= USB::ENDPOINT_COUNT {
return;
}
self.mutate(|_| crate::endpoint::set_stalled(self.regs, ep_addr, stalled));
}
fn is_stalled(&self, ep_addr: EndpointAddress) -> bool {
if ep_addr.index() >= USB::ENDPOINT_COUNT {
return true;
}
self.access(|_| crate::endpoint::is_stalled(self.regs, ep_addr))
.unwrap_or(true)
}
fn suspend(&self) {
// Nothing to do here?
}
fn resume(&self) {
// Nothing to do here?
}
fn poll(&self) -> PollResult {
self.access(|_| {
let regs = &self.regs;
let core_id = read_reg!(otg_global, regs.global(), CID);
let (wakeup, suspend, enum_done, reset, iep, rxflvl) = read_reg!(
otg_global,
regs.global(),
GINTSTS,
WKUPINT,
USBSUSP,
ENUMDNE,
USBRST,
IEPINT,
RXFLVL
);
if reset != 0 {
write_reg!(otg_global, regs.global(), GINTSTS, USBRST: 1);
self.deconfigure_all();
// Flush RX
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) != 0) {
return PollResult::None;
}
modify_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH: 1);
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, RXFFLSH) == 0) {
return PollResult::None;
}
}
if enum_done != 0 {
write_reg!(otg_global, regs.global(), GINTSTS, ENUMDNE: 1);
let speed = read_reg!(otg_device, regs.device(), DSTS, ENUMSPD);
// Compute and update TRDT
let trdt;
match speed {
0b00 => {
// High speed
// From RM0431 (F72xx), RM0090 (F429), RM0390 (F446)
if self.peripheral.ahb_frequency_hz() >= 30_000_000 {
trdt = 0x9;
} else {
panic!("AHB frequency is too low")
}
}
0b01 | 0b11 => {
// Full speed
// From RM0431 (F72xx), RM0090 (F429)
trdt = match self.peripheral.ahb_frequency_hz() {
0..=14_199_999 => panic!("AHB frequency is too low"),
14_200_000..=14_999_999 => 0xF,
15_000_000..=15_999_999 => 0xE,
16_000_000..=17_199_999 => 0xD,
17_200_000..=18_499_999 => 0xC,
18_500_000..=19_999_999 => 0xB,
20_000_000..=21_799_999 => 0xA,
21_800_000..=23_999_999 => 0x9,
24_000_000..=27_499_999 => 0x8,
27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE
32_000_000..=u32::MAX => 0x6,
};
}
_ => unimplemented!(),
}
modify_reg!(otg_global, regs.global(), GUSBCFG, TRDT: trdt);
PollResult::Reset
} else if wakeup != 0 {
// Clear the interrupt
write_reg!(otg_global, regs.global(), GINTSTS, WKUPINT: 1);
PollResult::Resume
} else if suspend != 0 {
write_reg!(otg_global, regs.global(), GINTSTS, USBSUSP: 1);
PollResult::Suspend
} else {
let mut ep_out = 0;
let mut ep_in_complete = 0;
let mut ep_setup = 0;
use crate::ral::{endpoint_in, endpoint_out};
// RXFLVL & IEPINT flags are read-only, there is no need to clear them
if rxflvl != 0 {
let (epnum, data_size, status) =
read_reg!(otg_global, regs.global(), GRXSTSR, EPNUM, BCNT, PKTSTS);
match status {
0x02 => {
// OUT received
ep_out |= 1 << epnum;
}
0x06 => {
// SETUP received
// flushing TX if something stuck in control endpoint
let ep = regs.endpoint_in(epnum as usize);
if read_reg!(endpoint_in, ep, DIEPTSIZ, PKTCNT) != 0 {
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, AHBIDL) != 0) {
return PollResult::None;
}
modify_reg!(otg_global, regs.global(), GRSTCTL, TXFNUM: epnum, TXFFLSH: 1);
if !self.wait(|| read_reg!(otg_global, regs.global(), GRSTCTL, TXFFLSH) == 0) {
return PollResult::None;
}
}
ep_setup |= 1 << epnum;
}
0x03 | 0x04 => {
// OUT completed | SETUP completed
// Re-enable the endpoint, F429-like chips only
if core_id == 0x0000_1200 || core_id == 0x0000_1100 {
let ep = regs.endpoint_out(epnum as usize);
modify_reg!(endpoint_out, ep, DOEPCTL, CNAK: 1, EPENA: 1);
}
read_reg!(otg_global, regs.global(), GRXSTSP); // pop GRXSTSP
}
_ => {
read_reg!(otg_global, regs.global(), GRXSTSP); // pop GRXSTSP
}
}
if status == 0x02 || status == 0x06 {
if let Some(ep) = &self.allocator.endpoints_out[epnum as usize] {
critical_section::with(|cs| {
let mut buffer = ep.buffer.borrow(cs).borrow_mut();
if buffer.state() == EndpointBufferState::Empty {
read_reg!(otg_global, regs.global(), GRXSTSP); // pop GRXSTSP
let is_setup = status == 0x06;
buffer
.fill_from_fifo(*regs, data_size as u16, is_setup)
.ok();
// Re-enable the endpoint, F446-like chips only
if core_id == 0x0000_2000
|| core_id == 0x0000_2100
|| core_id == 0x0000_2300
|| core_id == 0x0000_3000
|| core_id == 0x0000_3100
{
let ep = regs.endpoint_out(epnum as usize);
modify_reg!(endpoint_out, ep, DOEPCTL, CNAK: 1, EPENA: 1);
}
}
});
}
}
}
if iep != 0 {
for ep in &self.allocator.endpoints_in {
if let Some(ep) = ep {
let ep_regs = regs.endpoint_in(ep.address().index());
if read_reg!(endpoint_in, ep_regs, DIEPINT, XFRC) != 0 {
write_reg!(endpoint_in, ep_regs, DIEPINT, XFRC: 1);
ep_in_complete |= 1 << ep.address().index();
}
}
}
}
for ep in &self.allocator.endpoints_out {
if let Some(ep) = ep {
match ep.buffer_state() {
EndpointBufferState::DataOut => {
ep_out |= 1 << ep.address().index();
}
EndpointBufferState::DataSetup => {
ep_setup |= 1 << ep.address().index();
}
EndpointBufferState::Empty => {}
}
}
}
if (ep_in_complete | ep_out | ep_setup) != 0 {
PollResult::Data {
ep_out,
ep_in_complete,
ep_setup,
}
} else {
PollResult::None
}
}
}).unwrap_or(PollResult::None)
}
const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = true;
}
+242
View File
@@ -0,0 +1,242 @@
use crate::endpoint_memory::{EndpointBuffer, EndpointBufferState};
use crate::ral::{endpoint0_out, endpoint_in, endpoint_out, modify_reg, read_reg, write_reg};
use crate::target::{fifo_write, UsbRegisters};
use crate::transition::EndpointDescriptor;
use crate::UsbPeripheral;
use core::cell::RefCell;
use core::ops::{Deref, DerefMut};
use critical_section::Mutex;
use usb_device::endpoint::EndpointAddress;
use usb_device::{Result, UsbDirection, UsbError};
pub fn set_stalled(usb: UsbRegisters, address: EndpointAddress, stalled: bool) {
critical_section::with(|_| match address.direction() {
UsbDirection::Out => {
let ep = usb.endpoint_out(address.index() as usize);
modify_reg!(endpoint_out, ep, DOEPCTL, STALL: stalled as u32);
}
UsbDirection::In => {
let ep = usb.endpoint_in(address.index() as usize);
modify_reg!(endpoint_in, ep, DIEPCTL, STALL: stalled as u32);
}
})
}
pub fn is_stalled(usb: UsbRegisters, address: EndpointAddress) -> bool {
let stall = match address.direction() {
UsbDirection::Out => {
let ep = usb.endpoint_out(address.index());
read_reg!(endpoint_out, ep, DOEPCTL, STALL)
}
UsbDirection::In => {
let ep = usb.endpoint_in(address.index());
read_reg!(endpoint_in, ep, DIEPCTL, STALL)
}
};
stall != 0
}
/// Arbitrates access to the endpoint-specific registers and packet buffer memory.
pub struct Endpoint {
descriptor: EndpointDescriptor,
usb: UsbRegisters,
}
impl Endpoint {
pub fn new<USB: UsbPeripheral>(descriptor: EndpointDescriptor) -> Endpoint {
Endpoint {
descriptor,
usb: UsbRegisters::new::<USB>(),
}
}
pub fn address(&self) -> EndpointAddress {
self.descriptor.address
}
#[inline(always)]
fn index(&self) -> u8 {
self.descriptor.address.index() as u8
}
}
pub struct EndpointIn {
common: Endpoint,
}
impl EndpointIn {
pub fn new<USB: UsbPeripheral>(descriptor: EndpointDescriptor) -> EndpointIn {
EndpointIn {
common: Endpoint::new::<USB>(descriptor),
}
}
pub fn configure(&self) {
if self.index() == 0 {
let mpsiz = match self.descriptor.max_packet_size {
8 => 0b11,
16 => 0b10,
32 => 0b01,
64 => 0b00,
other => panic!("Unsupported EP0 size: {}", other),
};
let regs = self.usb.endpoint_in(self.index() as usize);
write_reg!(endpoint_in, regs, DIEPCTL, MPSIZ: mpsiz as u32, SNAK: 1);
write_reg!(endpoint_in, regs, DIEPTSIZ, PKTCNT: 0, XFRSIZ: self.descriptor.max_packet_size as u32);
} else {
let regs = self.usb.endpoint_in(self.index() as usize);
write_reg!(endpoint_in, regs, DIEPCTL,
SNAK: 1,
USBAEP: 1,
EPTYP: self.descriptor.ep_type.to_bm_attributes() as u32,
SD0PID_SEVNFRM: 1,
TXFNUM: self.index() as u32,
MPSIZ: self.descriptor.max_packet_size as u32
);
}
}
pub fn deconfigure(&self) {
let regs = self.usb.endpoint_in(self.index() as usize);
// deactivating endpoint
modify_reg!(endpoint_in, regs, DIEPCTL, USBAEP: 0);
// TODO: flushing FIFO
// disabling endpoint
if read_reg!(endpoint_in, regs, DIEPCTL, EPENA) != 0 && self.index() != 0 {
modify_reg!(endpoint_in, regs, DIEPCTL, EPDIS: 1)
}
// clean EP interrupts
write_reg!(endpoint_in, regs, DIEPINT, 0xff);
// TODO: deconfiguring TX FIFO
}
pub fn write(&self, buf: &[u8]) -> Result<()> {
let ep = self.usb.endpoint_in(self.index() as usize);
if self.index() != 0 && read_reg!(endpoint_in, ep, DIEPCTL, EPENA) != 0 {
return Err(UsbError::WouldBlock);
}
if buf.len() > self.descriptor.max_packet_size as usize {
return Err(UsbError::BufferOverflow);
}
if !buf.is_empty() {
// Check for FIFO free space
let size_words = (buf.len() + 3) / 4;
if size_words > read_reg!(endpoint_in, ep, DTXFSTS, INEPTFSAV) as usize {
return Err(UsbError::WouldBlock);
}
}
#[cfg(feature = "fs")]
write_reg!(endpoint_in, ep, DIEPTSIZ, PKTCNT: 1, XFRSIZ: buf.len() as u32);
#[cfg(feature = "hs")]
write_reg!(endpoint_in, ep, DIEPTSIZ, MCNT: 1, PKTCNT: 1, XFRSIZ: buf.len() as u32);
modify_reg!(endpoint_in, ep, DIEPCTL, CNAK: 1, EPENA: 1);
fifo_write(self.usb, self.index(), buf);
Ok(())
}
}
pub struct EndpointOut {
common: Endpoint,
pub(crate) buffer: Mutex<RefCell<EndpointBuffer>>,
}
impl EndpointOut {
pub fn new<USB: UsbPeripheral>(
descriptor: EndpointDescriptor,
buffer: EndpointBuffer,
) -> EndpointOut {
EndpointOut {
common: Endpoint::new::<USB>(descriptor),
buffer: Mutex::new(RefCell::new(buffer)),
}
}
pub fn configure(&self) {
if self.index() == 0 {
let mpsiz = match self.descriptor.max_packet_size {
8 => 0b11,
16 => 0b10,
32 => 0b01,
64 => 0b00,
other => panic!("Unsupported EP0 size: {}", other),
};
let regs = self.usb.endpoint0_out();
write_reg!(endpoint0_out, regs, DOEPTSIZ0, STUPCNT: 1, PKTCNT: 1, XFRSIZ: self.descriptor.max_packet_size as u32);
modify_reg!(endpoint0_out, regs, DOEPCTL0, MPSIZ: mpsiz as u32, EPENA: 1, CNAK: 1);
} else {
let regs = self.usb.endpoint_out(self.index() as usize);
write_reg!(endpoint_out, regs, DOEPCTL,
SD0PID_SEVNFRM: 1,
CNAK: 1,
EPENA: 1,
USBAEP: 1,
EPTYP: self.descriptor.ep_type.to_bm_attributes() as u32,
MPSIZ: self.descriptor.max_packet_size as u32
);
}
}
pub fn deconfigure(&self) {
let regs = self.usb.endpoint_out(self.index() as usize);
// deactivating endpoint
modify_reg!(endpoint_out, regs, DOEPCTL, USBAEP: 0);
// disabling endpoint
if read_reg!(endpoint_out, regs, DOEPCTL, EPENA) != 0 && self.index() != 0 {
modify_reg!(endpoint_out, regs, DOEPCTL, EPDIS: 1)
}
// clean EP interrupts
write_reg!(endpoint_out, regs, DOEPINT, 0xff);
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
critical_section::with(|cs| self.buffer.borrow(cs).borrow_mut().read_packet(buf))
}
pub fn buffer_state(&self) -> EndpointBufferState {
critical_section::with(|cs| self.buffer.borrow(cs).borrow().state())
}
}
impl Deref for EndpointIn {
type Target = Endpoint;
fn deref(&self) -> &Self::Target {
&self.common
}
}
impl DerefMut for EndpointIn {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.common
}
}
impl Deref for EndpointOut {
type Target = Endpoint;
fn deref(&self) -> &Self::Target {
&self.common
}
}
impl DerefMut for EndpointOut {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.common
}
}
+185
View File
@@ -0,0 +1,185 @@
#![allow(dead_code)]
use crate::target::{fifo_read_into, UsbRegisters};
use crate::UsbPeripheral;
use core::marker::PhantomData;
use core::slice;
use usb_device::{Result, UsbError};
use vcell::VolatileCell;
#[derive(Eq, PartialEq)]
pub enum EndpointBufferState {
Empty,
DataOut,
DataSetup,
}
pub struct EndpointBuffer {
buffer: &'static mut [VolatileCell<u32>],
data_size: u16,
has_data: bool,
is_setup: bool,
}
impl EndpointBuffer {
pub fn new(buffer: &'static mut [u32]) -> Self {
Self {
buffer: unsafe { &mut *(buffer as *mut [u32] as *mut [VolatileCell<u32>]) },
data_size: 0,
has_data: false,
is_setup: false,
}
}
pub fn read_packet(&mut self, mut buf: &mut [u8]) -> Result<usize> {
if !self.has_data {
return Err(UsbError::WouldBlock);
}
let data_size = self.data_size as usize;
if buf.len() < data_size {
return Err(UsbError::BufferOverflow);
}
let mut index = 0;
let mut current_size = data_size;
while current_size >= 4 {
let word = self.buffer[index].get();
index += 1;
let bytes = word.to_ne_bytes();
buf[..4].copy_from_slice(&bytes);
buf = &mut buf[4..];
current_size -= 4;
}
if current_size > 0 {
let word = self.buffer[index].get();
let bytes = word.to_ne_bytes();
buf[..current_size].copy_from_slice(&bytes[..current_size]);
}
self.has_data = false;
Ok(data_size)
}
pub fn fill_from_fifo(
&mut self,
usb: UsbRegisters,
data_size: u16,
is_setup: bool,
) -> Result<()> {
if self.has_data {
return Err(UsbError::WouldBlock);
}
if data_size as usize > self.capacity() {
return Err(UsbError::BufferOverflow);
}
let words = (data_size as usize + 3) / 4;
fifo_read_into(usb, &self.buffer[..words]);
self.is_setup = is_setup;
self.data_size = data_size;
self.has_data = true;
Ok(())
}
pub fn state(&self) -> EndpointBufferState {
if self.has_data {
if self.is_setup {
EndpointBufferState::DataSetup
} else {
EndpointBufferState::DataOut
}
} else {
EndpointBufferState::Empty
}
}
pub fn capacity(&self) -> usize {
self.buffer.len() * 4
}
}
impl Default for EndpointBuffer {
fn default() -> Self {
EndpointBuffer::new(&mut [])
}
}
pub struct EndpointMemoryAllocator<USB> {
next_free_offset: usize,
max_size_words: usize,
memory: &'static mut [u32],
tx_fifo_size_words: [u16; 9],
_marker: PhantomData<USB>,
}
impl<USB: UsbPeripheral> EndpointMemoryAllocator<USB> {
pub fn new(memory: &'static mut [u32]) -> Self {
Self {
next_free_offset: 0,
max_size_words: 0,
memory,
tx_fifo_size_words: [0; 9],
_marker: PhantomData,
}
}
pub fn allocate_rx_buffer(&mut self, size: usize) -> Result<EndpointBuffer> {
let size_words = (size + 3) / 4;
let offset = self.next_free_offset;
if offset + size_words > self.memory.len() {
return Err(UsbError::EndpointMemoryOverflow);
}
self.next_free_offset += size_words;
self.max_size_words = core::cmp::max(self.max_size_words, size_words);
let buffer = unsafe {
let ptr = self.memory.as_mut_ptr().add(offset);
slice::from_raw_parts_mut(ptr, size_words)
};
Ok(EndpointBuffer::new(buffer))
}
pub fn allocate_tx_buffer(&mut self, ep_number: u8, size: usize) -> Result<()> {
let ep_number = ep_number as usize;
assert!(ep_number < self.tx_fifo_size_words.len());
if self.tx_fifo_size_words[ep_number] != 0 {
return Err(UsbError::InvalidEndpoint);
}
let used = 30
+ self.total_rx_buffer_size_words() as usize
+ self.tx_fifo_size_words.iter().sum::<u16>() as usize;
let size_words = core::cmp::max((size + 3) / 4, 16);
if (used + size_words) > USB::FIFO_DEPTH_WORDS {
return Err(UsbError::EndpointMemoryOverflow);
}
self.tx_fifo_size_words[ep_number] = size_words as u16;
Ok(())
}
/// Returns the size of memory allocated for OUT endpoints in words
pub fn total_rx_buffer_size_words(&self) -> u16 {
self.next_free_offset as u16
}
pub fn tx_fifo_size_words(&self, ep_number: usize) -> u16 {
self.tx_fifo_size_words[ep_number]
}
pub fn max_buffer_size_words(&self) -> usize {
self.max_size_words
}
}
+90
View File
@@ -0,0 +1,90 @@
//! USB peripheral driver for Synopsys USB OTG peripherals.
#![no_std]
#[cfg(all(feature = "fs", feature = "hs"))]
compile_error!("choose only one USB mode");
#[cfg(not(any(feature = "fs", feature = "hs")))]
compile_error!("select USB mode feature (fs/hs)");
mod endpoint;
mod endpoint_memory;
mod target;
/// USB peripheral driver.
pub mod bus;
pub use crate::bus::UsbBus;
mod ral;
mod transition;
/// A trait for device-specific USB peripherals. Implement this to add support for a new hardware
/// platform. Peripherals that have this trait must have the same register block as STM32 USB OTG
/// peripherals.
pub unsafe trait UsbPeripheral: Send + Sync {
/// Pointer to the register block
const REGISTERS: *const ();
/// true for High Speed variants of the peripheral, false for Full Speed
const HIGH_SPEED: bool;
/// FIFO size in 32-bit words
const FIFO_DEPTH_WORDS: usize;
/// Number of (bidirectional) endpoints
const ENDPOINT_COUNT: usize;
/// Enables USB device on its peripheral bus
fn enable();
/// AHB frequency in hertz
fn ahb_frequency_hz(&self) -> u32;
/// Maximum elapsed time for one register wait, in microseconds.
const WAIT_TIMEOUT_US: u32;
/// Independent finite limit if the clock fails to advance.
const WAIT_POLL_LIMIT: u32;
/// Reads a monotonic clock which advances without USB or timer interrupts.
/// This must be bounded and must not block or panic.
fn now_us(&self) -> u64;
/// Permanently stops this peripheral after failure.
///
/// This must be bounded, synchronous, and must not panic, wait, reenter this bus,
/// or access USB registers. It runs without the driver's critical section.
/// Holding the peripheral in its external reset is a suitable implementation.
fn abort(&self);
/// Returns PHY type that should be used for USB peripheral
fn phy_type(&self) -> PhyType {
PhyType::InternalFullSpeed
}
/// Performs initial setup of the internal high-speed PHY
///
/// This function should turn on LDO and PLL and wait for PHY clock to become stable.
fn setup_internal_hs_phy(&self) {}
}
/// USB PHY type
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PhyType {
/// Internal Full-Speed PHY
///
/// Available on most High-Speed peripherals.
InternalFullSpeed,
/// Internal High-Speed PHY
///
/// Available on a few STM32 chips.
InternalHighSpeed,
/// External ULPI High-Speed PHY
ExternalHighSpeed,
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,8 @@
#![allow(unused)]
pub mod otg_fs_device_f401_f405_f407_f411_f427_f429;
pub mod otg_fs_global_f401_f405_f407_f411_f427_f429;
pub mod otg_fs_host;
pub mod otg_hs_device;
pub mod otg_hs_global;
pub mod otg_hs_host;
pub mod otg_s_pwrclk;
@@ -0,0 +1,83 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f427, stm32f429
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_device_v1::Instance;
pub use super::super::peripherals::otg_fs_device_v1::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_fs_device_v1::{
DAINT, DAINTMSK, DCFG, DCTL, DIEPCTL0, DIEPCTL1, DIEPCTL2, DIEPCTL3, DIEPEMPMSK, DIEPINT0,
DIEPINT1, DIEPINT2, DIEPINT3, DIEPMSK, DIEPTSIZ0, DIEPTSIZ1, DIEPTSIZ2, DIEPTSIZ3, DOEPCTL0,
DOEPCTL1, DOEPCTL2, DOEPCTL3, DOEPINT0, DOEPINT1, DOEPINT2, DOEPINT3, DOEPMSK, DOEPTSIZ0,
DOEPTSIZ1, DOEPTSIZ2, DOEPTSIZ3, DSTS, DTXFSTS0, DTXFSTS1, DTXFSTS2, DTXFSTS3, DVBUSDIS,
DVBUSPULSE,
};
/// Access functions for the OTG_FS_DEVICE peripheral instance
pub mod OTG_FS_DEVICE {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000800,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_DEVICE
pub const reset: ResetValues = ResetValues {
DCFG: 0x02200000,
DCTL: 0x00000000,
DSTS: 0x00000010,
DIEPMSK: 0x00000000,
DOEPMSK: 0x00000000,
DAINT: 0x00000000,
DAINTMSK: 0x00000000,
DVBUSDIS: 0x000017D7,
DVBUSPULSE: 0x000005B8,
DIEPEMPMSK: 0x00000000,
DIEPCTL0: 0x00000000,
DIEPCTL1: 0x00000000,
DIEPCTL2: 0x00000000,
DIEPCTL3: 0x00000000,
DOEPCTL0: 0x00008000,
DOEPCTL1: 0x00000000,
DOEPCTL2: 0x00000000,
DOEPCTL3: 0x00000000,
DIEPINT0: 0x00000080,
DIEPINT1: 0x00000080,
DIEPINT2: 0x00000080,
DIEPINT3: 0x00000080,
DOEPINT0: 0x00000080,
DOEPINT1: 0x00000080,
DOEPINT2: 0x00000080,
DOEPINT3: 0x00000080,
DIEPTSIZ0: 0x00000000,
DOEPTSIZ0: 0x00000000,
DIEPTSIZ1: 0x00000000,
DIEPTSIZ2: 0x00000000,
DIEPTSIZ3: 0x00000000,
DTXFSTS0: 0x00000000,
DTXFSTS1: 0x00000000,
DTXFSTS2: 0x00000000,
DTXFSTS3: 0x00000000,
DOEPTSIZ1: 0x00000000,
DOEPTSIZ2: 0x00000000,
DOEPTSIZ3: 0x00000000,
};
}
/// Raw pointer to OTG_FS_DEVICE
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_DEVICE: *const RegisterBlock = 0x50000800 as *const _;
@@ -0,0 +1,60 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f427, stm32f429
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_global_v1::Instance;
pub use super::super::peripherals::otg_fs_global_v1::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_fs_global_v1::{
CID, DIEPTXF0, DIEPTXF1, DIEPTXF2, DIEPTXF3, GAHBCFG, GCCFG, GINTMSK, GINTSTS, GNPTXSTS,
GOTGCTL, GOTGINT, GRSTCTL, GRXFSIZ, GRXSTSP, GRXSTSR, GUSBCFG, HPTXFSIZ,
};
/// Access functions for the OTG_FS_GLOBAL peripheral instance
pub mod OTG_FS_GLOBAL {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000000,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_GLOBAL
pub const reset: ResetValues = ResetValues {
GOTGCTL: 0x00000800,
GOTGINT: 0x00000000,
GAHBCFG: 0x00000000,
GUSBCFG: 0x00000A00,
GRSTCTL: 0x20000000,
GINTSTS: 0x04000020,
GINTMSK: 0x00000000,
GRXSTSR: 0x00000000,
GRXFSIZ: 0x00000200,
DIEPTXF0: 0x00000200,
GNPTXSTS: 0x00080200,
GCCFG: 0x00000000,
CID: 0x00001000,
HPTXFSIZ: 0x02000600,
DIEPTXF1: 0x02000400,
DIEPTXF2: 0x02000400,
DIEPTXF3: 0x02000400,
GRXSTSP: 0x00000000,
};
}
/// Raw pointer to OTG_FS_GLOBAL
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_GLOBAL: *const RegisterBlock = 0x50000000 as *const _;
@@ -0,0 +1,83 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f412, stm32f413, stm32f427, stm32f429, stm32f446, stm32f469
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_host::Instance;
pub use super::super::peripherals::otg_fs_host::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_fs_host::{
HAINT, HAINTMSK, HCCHAR0, HCCHAR1, HCCHAR2, HCCHAR3, HCCHAR4, HCCHAR5, HCCHAR6, HCCHAR7, HCFG,
HCINT0, HCINT1, HCINT2, HCINT3, HCINT4, HCINT5, HCINT6, HCINT7, HCINTMSK0, HCINTMSK1,
HCINTMSK2, HCINTMSK3, HCINTMSK4, HCINTMSK5, HCINTMSK6, HCINTMSK7, HCTSIZ0, HCTSIZ1, HCTSIZ2,
HCTSIZ3, HCTSIZ4, HCTSIZ5, HCTSIZ6, HCTSIZ7, HFIR, HFNUM, HPRT, HPTXSTS,
};
/// Access functions for the OTG_FS_HOST peripheral instance
pub mod OTG_FS_HOST {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000400,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_HOST
pub const reset: ResetValues = ResetValues {
HCFG: 0x00000000,
HFIR: 0x0000EA60,
HFNUM: 0x00003FFF,
HPTXSTS: 0x00080100,
HAINT: 0x00000000,
HAINTMSK: 0x00000000,
HPRT: 0x00000000,
HCCHAR0: 0x00000000,
HCCHAR1: 0x00000000,
HCCHAR2: 0x00000000,
HCCHAR3: 0x00000000,
HCCHAR4: 0x00000000,
HCCHAR5: 0x00000000,
HCCHAR6: 0x00000000,
HCCHAR7: 0x00000000,
HCINT0: 0x00000000,
HCINT1: 0x00000000,
HCINT2: 0x00000000,
HCINT3: 0x00000000,
HCINT4: 0x00000000,
HCINT5: 0x00000000,
HCINT6: 0x00000000,
HCINT7: 0x00000000,
HCINTMSK0: 0x00000000,
HCINTMSK1: 0x00000000,
HCINTMSK2: 0x00000000,
HCINTMSK3: 0x00000000,
HCINTMSK4: 0x00000000,
HCINTMSK5: 0x00000000,
HCINTMSK6: 0x00000000,
HCINTMSK7: 0x00000000,
HCTSIZ0: 0x00000000,
HCTSIZ1: 0x00000000,
HCTSIZ2: 0x00000000,
HCTSIZ3: 0x00000000,
HCTSIZ4: 0x00000000,
HCTSIZ5: 0x00000000,
HCTSIZ6: 0x00000000,
HCTSIZ7: 0x00000000,
};
}
/// Raw pointer to OTG_FS_HOST
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_HOST: *const RegisterBlock = 0x50000400 as *const _;
@@ -0,0 +1,109 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go high speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_hs_device::Instance;
pub use super::super::peripherals::otg_hs_device::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_hs_device::{
DAINT, DAINTMSK, DCFG, DCTL, DEACHINT, DEACHINTMSK, DIEPCTL0, DIEPCTL1, DIEPCTL2, DIEPCTL3,
DIEPCTL4, DIEPCTL5, DIEPDMA1, DIEPDMA2, DIEPDMA3, DIEPDMA4, DIEPDMA5, DIEPEACHMSK1, DIEPEMPMSK,
DIEPINT0, DIEPINT1, DIEPINT2, DIEPINT3, DIEPINT4, DIEPINT5, DIEPMSK, DIEPTSIZ0, DIEPTSIZ1,
DIEPTSIZ2, DIEPTSIZ3, DIEPTSIZ4, DIEPTSIZ5, DOEPCTL0, DOEPCTL1, DOEPCTL2, DOEPCTL3, DOEPCTL4,
DOEPCTL5, DOEPEACHMSK1, DOEPINT0, DOEPINT1, DOEPINT2, DOEPINT3, DOEPINT4, DOEPINT5, DOEPMSK,
DOEPTSIZ0, DOEPTSIZ1, DOEPTSIZ2, DOEPTSIZ3, DOEPTSIZ4, DOEPTSIZ5, DSTS, DTHRCTL, DTXFSTS0,
DTXFSTS1, DTXFSTS2, DTXFSTS3, DTXFSTS4, DTXFSTS5, DVBUSDIS, DVBUSPULSE,
};
/// Access functions for the OTG_HS_DEVICE peripheral instance
pub mod OTG_HS_DEVICE {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040800,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_DEVICE
pub const reset: ResetValues = ResetValues {
DCFG: 0x02200000,
DCTL: 0x00000000,
DSTS: 0x00000010,
DIEPMSK: 0x00000000,
DOEPMSK: 0x00000000,
DAINT: 0x00000000,
DAINTMSK: 0x00000000,
DVBUSDIS: 0x000017D7,
DVBUSPULSE: 0x000005B8,
DTHRCTL: 0x00000000,
DIEPEMPMSK: 0x00000000,
DEACHINT: 0x00000000,
DEACHINTMSK: 0x00000000,
DIEPEACHMSK1: 0x00000000,
DOEPEACHMSK1: 0x00000000,
DIEPCTL0: 0x00000000,
DIEPCTL1: 0x00000000,
DIEPCTL2: 0x00000000,
DIEPCTL3: 0x00000000,
DIEPCTL4: 0x00000000,
DIEPCTL5: 0x00000000,
DIEPINT0: 0x00000080,
DIEPINT1: 0x00000080,
DIEPINT2: 0x00000080,
DIEPINT3: 0x00000080,
DIEPINT4: 0x00000080,
DIEPINT5: 0x00000080,
DIEPTSIZ0: 0x00000000,
DIEPDMA1: 0x00000000,
DIEPDMA2: 0x00000000,
DIEPDMA3: 0x00000000,
DIEPDMA4: 0x00000000,
DIEPDMA5: 0x00000000,
DTXFSTS0: 0x00000000,
DTXFSTS1: 0x00000000,
DTXFSTS2: 0x00000000,
DTXFSTS3: 0x00000000,
DTXFSTS4: 0x00000000,
DTXFSTS5: 0x00000000,
DIEPTSIZ1: 0x00000000,
DIEPTSIZ2: 0x00000000,
DIEPTSIZ3: 0x00000000,
DIEPTSIZ4: 0x00000000,
DIEPTSIZ5: 0x00000000,
DOEPCTL0: 0x00008000,
DOEPCTL1: 0x00000000,
DOEPCTL2: 0x00000000,
DOEPCTL3: 0x00000000,
DOEPCTL4: 0x00000000,
DOEPCTL5: 0x00000000,
DOEPINT0: 0x00000080,
DOEPINT1: 0x00000080,
DOEPINT2: 0x00000080,
DOEPINT3: 0x00000080,
DOEPINT4: 0x00000080,
DOEPINT5: 0x00000080,
DOEPTSIZ0: 0x00000000,
DOEPTSIZ1: 0x00000000,
DOEPTSIZ2: 0x00000000,
DOEPTSIZ3: 0x00000000,
DOEPTSIZ4: 0x00000000,
DOEPTSIZ5: 0x00000000,
};
}
/// Raw pointer to OTG_HS_DEVICE
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_DEVICE: *const RegisterBlock = 0x40040800 as *const _;
@@ -0,0 +1,64 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go high speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_hs_global::Instance;
pub use super::super::peripherals::otg_hs_global::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_hs_global::{
CID, DIEPTXF1, DIEPTXF2, DIEPTXF3, DIEPTXF4, DIEPTXF5, GAHBCFG, GCCFG, GINTMSK, GINTSTS,
GNPTXFSIZ, GNPTXSTS, GOTGCTL, GOTGINT, GRSTCTL, GRXFSIZ, GRXSTSP, GRXSTSR, GUSBCFG, HPTXFSIZ,
PHYCR,
};
/// Access functions for the OTG_HS_GLOBAL peripheral instance
pub mod OTG_HS_GLOBAL {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040000,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_GLOBAL
pub const reset: ResetValues = ResetValues {
GOTGCTL: 0x00000800,
GOTGINT: 0x00000000,
GAHBCFG: 0x00000000,
GUSBCFG: 0x00000A00,
GRSTCTL: 0x20000000,
GINTSTS: 0x04000020,
GINTMSK: 0x00000000,
GRXSTSR: 0x00000000,
GRXSTSP: 0x00000000,
GRXFSIZ: 0x00000200,
GNPTXFSIZ: 0x00000200,
GNPTXSTS: 0x00080200,
PHYCR: 0x00000000,
GCCFG: 0x00000000,
CID: 0x00001200,
HPTXFSIZ: 0x02000600,
DIEPTXF1: 0x02000400,
DIEPTXF2: 0x02000400,
DIEPTXF3: 0x02000400,
DIEPTXF4: 0x02000400,
DIEPTXF5: 0x02000400,
};
}
/// Raw pointer to OTG_HS_GLOBAL
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_GLOBAL: *const RegisterBlock = 0x40040000 as *const _;
@@ -0,0 +1,127 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go high speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446, stm32f469
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_hs_host::Instance;
pub use super::super::peripherals::otg_hs_host::{RegisterBlock, ResetValues};
pub use super::super::peripherals::otg_hs_host::{
HAINT, HAINTMSK, HCCHAR0, HCCHAR1, HCCHAR10, HCCHAR11, HCCHAR2, HCCHAR3, HCCHAR4, HCCHAR5,
HCCHAR6, HCCHAR7, HCCHAR8, HCCHAR9, HCDMA0, HCDMA1, HCDMA10, HCDMA11, HCDMA2, HCDMA3, HCDMA4,
HCDMA5, HCDMA6, HCDMA7, HCDMA8, HCDMA9, HCFG, HCINT0, HCINT1, HCINT10, HCINT11, HCINT2, HCINT3,
HCINT4, HCINT5, HCINT6, HCINT7, HCINT8, HCINT9, HCINTMSK0, HCINTMSK1, HCINTMSK10, HCINTMSK11,
HCINTMSK2, HCINTMSK3, HCINTMSK4, HCINTMSK5, HCINTMSK6, HCINTMSK7, HCINTMSK8, HCINTMSK9,
HCSPLT0, HCSPLT1, HCSPLT10, HCSPLT11, HCSPLT2, HCSPLT3, HCSPLT4, HCSPLT5, HCSPLT6, HCSPLT7,
HCSPLT8, HCSPLT9, HCTSIZ0, HCTSIZ1, HCTSIZ10, HCTSIZ11, HCTSIZ2, HCTSIZ3, HCTSIZ4, HCTSIZ5,
HCTSIZ6, HCTSIZ7, HCTSIZ8, HCTSIZ9, HFIR, HFNUM, HPRT, HPTXSTS,
};
/// Access functions for the OTG_HS_HOST peripheral instance
pub mod OTG_HS_HOST {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040400,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_HOST
pub const reset: ResetValues = ResetValues {
HCFG: 0x00000000,
HFIR: 0x0000EA60,
HFNUM: 0x00003FFF,
HPTXSTS: 0x00080100,
HAINT: 0x00000000,
HAINTMSK: 0x00000000,
HPRT: 0x00000000,
HCCHAR0: 0x00000000,
HCCHAR1: 0x00000000,
HCCHAR2: 0x00000000,
HCCHAR3: 0x00000000,
HCCHAR4: 0x00000000,
HCCHAR5: 0x00000000,
HCCHAR6: 0x00000000,
HCCHAR7: 0x00000000,
HCCHAR8: 0x00000000,
HCCHAR9: 0x00000000,
HCCHAR10: 0x00000000,
HCCHAR11: 0x00000000,
HCSPLT0: 0x00000000,
HCSPLT1: 0x00000000,
HCSPLT2: 0x00000000,
HCSPLT3: 0x00000000,
HCSPLT4: 0x00000000,
HCSPLT5: 0x00000000,
HCSPLT6: 0x00000000,
HCSPLT7: 0x00000000,
HCSPLT8: 0x00000000,
HCSPLT9: 0x00000000,
HCSPLT10: 0x00000000,
HCSPLT11: 0x00000000,
HCINT0: 0x00000000,
HCINT1: 0x00000000,
HCINT2: 0x00000000,
HCINT3: 0x00000000,
HCINT4: 0x00000000,
HCINT5: 0x00000000,
HCINT6: 0x00000000,
HCINT7: 0x00000000,
HCINT8: 0x00000000,
HCINT9: 0x00000000,
HCINT10: 0x00000000,
HCINT11: 0x00000000,
HCINTMSK0: 0x00000000,
HCINTMSK1: 0x00000000,
HCINTMSK2: 0x00000000,
HCINTMSK3: 0x00000000,
HCINTMSK4: 0x00000000,
HCINTMSK5: 0x00000000,
HCINTMSK6: 0x00000000,
HCINTMSK7: 0x00000000,
HCINTMSK8: 0x00000000,
HCINTMSK9: 0x00000000,
HCINTMSK10: 0x00000000,
HCINTMSK11: 0x00000000,
HCTSIZ0: 0x00000000,
HCTSIZ1: 0x00000000,
HCTSIZ2: 0x00000000,
HCTSIZ3: 0x00000000,
HCTSIZ4: 0x00000000,
HCTSIZ5: 0x00000000,
HCTSIZ6: 0x00000000,
HCTSIZ7: 0x00000000,
HCTSIZ8: 0x00000000,
HCTSIZ9: 0x00000000,
HCTSIZ10: 0x00000000,
HCTSIZ11: 0x00000000,
HCDMA0: 0x00000000,
HCDMA1: 0x00000000,
HCDMA2: 0x00000000,
HCDMA3: 0x00000000,
HCDMA4: 0x00000000,
HCDMA5: 0x00000000,
HCDMA6: 0x00000000,
HCDMA7: 0x00000000,
HCDMA8: 0x00000000,
HCDMA9: 0x00000000,
HCDMA10: 0x00000000,
HCDMA11: 0x00000000,
};
}
/// Raw pointer to OTG_HS_HOST
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_HOST: *const RegisterBlock = 0x40040400 as *const _;
@@ -0,0 +1,70 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f405, stm32f407, stm32f427, stm32f429, stm32f446
#[cfg(not(feature = "nosync"))]
pub use super::super::peripherals::otg_fs_pwrclk::Instance;
pub use super::super::peripherals::otg_fs_pwrclk::PCGCCTL;
pub use super::super::peripherals::otg_fs_pwrclk::{RegisterBlock, ResetValues};
/// Access functions for the OTG_FS_PWRCLK peripheral instance
pub mod OTG_FS_PWRCLK {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x50000e00,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_FS_PWRCLK
pub const reset: ResetValues = ResetValues {
PCGCCTL: 0x00000000,
};
}
/// Raw pointer to OTG_FS_PWRCLK
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_FS_PWRCLK: *const RegisterBlock = 0x50000e00 as *const _;
/// Access functions for the OTG_HS_PWRCLK peripheral instance
pub mod OTG_HS_PWRCLK {
use super::ResetValues;
#[cfg(not(feature = "nosync"))]
use super::Instance;
#[cfg(not(feature = "nosync"))]
const INSTANCE: Instance = Instance {
addr: 0x40040e00,
_marker: ::core::marker::PhantomData,
};
/// Reset values for each field in OTG_HS_PWRCLK
pub const reset: ResetValues = ResetValues {
PCGCCTL: 0x00000000,
};
}
/// Raw pointer to OTG_HS_PWRCLK
///
/// Dereferencing this is unsafe because you are not ensured unique
/// access to the peripheral, so you may encounter data races with
/// other users of this peripheral. It is up to you to ensure you
/// will not cause data races.
///
/// This constant is provided for ease of use in unsafe code: you can
/// simply call for example `write_reg!(gpio, GPIOA, ODR, 1);`.
pub const OTG_HS_PWRCLK: *const RegisterBlock = 0x40040e00 as *const _;
+118
View File
@@ -0,0 +1,118 @@
#![allow(non_snake_case)]
pub mod instances;
pub mod peripherals;
pub mod register;
pub mod stm32f429;
pub use crate::{modify_reg, read_reg, write_reg};
pub mod otg_global {
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_global::OTG_FS_GLOBAL as OTG_GLOBAL;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_global::*;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_global::OTG_HS_GLOBAL as OTG_GLOBAL;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_global::*;
}
pub mod otg_device {
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::OTG_FS_DEVICE as OTG_DEVICE;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::*;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::OTG_HS_DEVICE as OTG_DEVICE;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::*;
}
pub mod otg_pwrclk {
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_s_pwrclk::OTG_FS_PWRCLK as OTG_PWRCLK;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_s_pwrclk::OTG_HS_PWRCLK as OTG_PWRCLK;
pub use super::stm32f429::otg_s_pwrclk::*;
}
pub mod otg_global_dieptxfx {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_global::DIEPTXF1 as DIEPTXFx;
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_fs_global::DIEPTXF1 as DIEPTXFx;
pub struct RegisterBlock {
pub DIEPTXFx: RWRegister<u32>,
}
}
pub mod endpoint_in {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::{
DIEPCTL1 as DIEPCTL, DIEPINT1 as DIEPINT, DIEPTSIZ1 as DIEPTSIZ, DTXFSTS1 as DTXFSTS,
};
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::{
DIEPCTL1 as DIEPCTL, DIEPINT1 as DIEPINT, DIEPTSIZ1 as DIEPTSIZ, DTXFSTS1 as DTXFSTS,
};
pub struct RegisterBlock {
pub DIEPCTL: RWRegister<u32>,
_reserved0: u32,
pub DIEPINT: RWRegister<u32>,
_reserved1: u32,
pub DIEPTSIZ: RWRegister<u32>,
_reserved2: u32,
pub DTXFSTS: RWRegister<u32>,
_reserved3: u32,
}
}
pub mod endpoint0_out {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::{DOEPCTL0, DOEPINT0, DOEPTSIZ0};
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::{DOEPCTL0, DOEPINT0, DOEPTSIZ0};
pub struct RegisterBlock {
pub DOEPCTL0: RWRegister<u32>,
_reserved0: u32,
pub DOEPINT0: RWRegister<u32>,
_reserved1: u32,
pub DOEPTSIZ0: RWRegister<u32>,
_reserved2: [u32; 3],
}
}
pub mod endpoint_out {
use super::register::RWRegister;
#[cfg(feature = "fs")]
pub use super::stm32f429::otg_fs_device::{
DOEPCTL1 as DOEPCTL, DOEPINT1 as DOEPINT, DOEPTSIZ1 as DOEPTSIZ,
};
#[cfg(feature = "hs")]
pub use super::stm32f429::otg_hs_device::{
DOEPCTL1 as DOEPCTL, DOEPINT1 as DOEPINT, DOEPTSIZ1 as DOEPTSIZ,
};
pub struct RegisterBlock {
pub DOEPCTL: RWRegister<u32>,
_reserved0: u32,
pub DOEPINT: RWRegister<u32>,
_reserved1: u32,
pub DOEPTSIZ: RWRegister<u32>,
_reserved2: [u32; 3],
}
}
@@ -0,0 +1,8 @@
#![allow(unused)]
pub mod otg_fs_device_v1;
pub mod otg_fs_global_v1;
pub mod otg_fs_host;
pub mod otg_fs_pwrclk;
pub mod otg_hs_device;
pub mod otg_hs_global;
pub mod otg_hs_host;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,78 @@
#![allow(non_snake_case, non_upper_case_globals)]
#![allow(non_camel_case_types)]
//! USB on the go full speed
//!
//! Used by: stm32f401, stm32f405, stm32f407, stm32f411, stm32f412, stm32f413, stm32f427, stm32f429, stm32f446, stm32f469
use super::super::register::RWRegister;
#[cfg(not(feature = "nosync"))]
use core::marker::PhantomData;
/// OTG_FS power and clock gating control register
pub mod PCGCCTL {
/// Stop PHY clock
pub mod STPPCLK {
/// Offset (0 bits)
pub const offset: u32 = 0;
/// Mask (1 bit: 1 << 0)
pub const mask: u32 = 1 << offset;
/// Read-only values (empty)
pub mod R {}
/// Write-only values (empty)
pub mod W {}
/// Read-write values (empty)
pub mod RW {}
}
/// Gate HCLK
pub mod GATEHCLK {
/// Offset (1 bits)
pub const offset: u32 = 1;
/// Mask (1 bit: 1 << 1)
pub const mask: u32 = 1 << offset;
/// Read-only values (empty)
pub mod R {}
/// Write-only values (empty)
pub mod W {}
/// Read-write values (empty)
pub mod RW {}
}
/// PHY Suspended
pub mod PHYSUSP {
/// Offset (4 bits)
pub const offset: u32 = 4;
/// Mask (1 bit: 1 << 4)
pub const mask: u32 = 1 << offset;
/// Read-only values (empty)
pub mod R {}
/// Write-only values (empty)
pub mod W {}
/// Read-write values (empty)
pub mod RW {}
}
}
#[repr(C)]
pub struct RegisterBlock {
/// OTG_FS power and clock gating control register
pub PCGCCTL: RWRegister<u32>,
}
pub struct ResetValues {
pub PCGCCTL: u32,
}
#[cfg(not(feature = "nosync"))]
pub struct Instance {
pub(crate) addr: u32,
pub(crate) _marker: PhantomData<*const RegisterBlock>,
}
#[cfg(not(feature = "nosync"))]
impl ::core::ops::Deref for Instance {
type Target = RegisterBlock;
#[inline(always)]
fn deref(&self) -> &RegisterBlock {
unsafe { &*(self.addr as *const _) }
}
}
#[cfg(feature = "rtfm")]
unsafe impl Send for Instance {}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+610
View File
@@ -0,0 +1,610 @@
#![allow(unused)]
use core::cell::UnsafeCell;
/// A read-write register of type T.
///
/// Contains one value of type T and provides volatile read/write functions to it.
///
/// # Safety
/// This register should be used where reads and writes to this peripheral register do not
/// lead to memory unsafety. For example, it is a poor choice for a DMA target, but less
/// worrisome for a GPIO output data register.
///
/// Access to this register must be synchronised; if multiple threads (or the main thread and an
/// interrupt service routine) are accessing it simultaneously you may encounter data races.
pub struct RWRegister<T> {
register: UnsafeCell<T>,
}
impl<T: Copy> RWRegister<T> {
/// Reads the value of the register.
#[inline(always)]
pub fn read(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.register.get()) }
}
/// Writes a new value to the register.
#[inline(always)]
pub fn write(&self, val: T) {
unsafe { ::core::ptr::write_volatile(self.register.get(), val) }
}
}
/// A read-write register of type T, where read/write access is unsafe.
///
/// Contains one value of type T and provides volatile read/write functions to it.
///
/// # Safety
/// This register should be used where reads and writes to this peripheral may invoke
/// undefined behaviour or memory unsafety. For example, any registers you write a memory
/// address into.
///
/// Access to this register must be synchronised; if multiple threads (or the main thread and an
/// interrupt service routine) are accessing it simultaneously you may encounter data races.
pub struct UnsafeRWRegister<T> {
register: UnsafeCell<T>,
}
impl<T: Copy> UnsafeRWRegister<T> {
/// Reads the value of the register.
#[inline(always)]
pub unsafe fn read(&self) -> T {
::core::ptr::read_volatile(self.register.get())
}
/// Writes a new value to the register.
#[inline(always)]
pub unsafe fn write(&self, val: T) {
::core::ptr::write_volatile(self.register.get(), val)
}
}
/// A read-only register of type T.
///
/// Contains one value of type T and provides a volatile read function to it.
///
/// # Safety
/// This register should be used where reads and writes to this peripheral register do not
/// lead to memory unsafety.
///
/// Access to this register must be synchronised; if multiple threads (or the main thread and an
/// interrupt service routine) are accessing it simultaneously you may encounter data races.
pub struct RORegister<T> {
register: UnsafeCell<T>,
}
impl<T: Copy> RORegister<T> {
/// Reads the value of the register.
#[inline(always)]
pub fn read(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.register.get()) }
}
}
/// A read-only register of type T, where read access is unsafe.
///
/// Contains one value of type T and provides a volatile read function to it.
///
/// # Safety
/// This register should be used where reads to this peripheral may invoke
/// undefined behaviour or memory unsafety.
///
/// Access to this register must be synchronised; if multiple threads (or the main thread and an
/// interrupt service routine) are accessing it simultaneously you may encounter data races.
pub struct UnsafeRORegister<T> {
register: UnsafeCell<T>,
}
impl<T: Copy> UnsafeRORegister<T> {
/// Reads the value of the register.
#[inline(always)]
pub unsafe fn read(&self) -> T {
::core::ptr::read_volatile(self.register.get())
}
}
/// A write-only register of type T.
///
/// Contains one value of type T and provides a volatile write function to it.
///
/// # Safety
/// This register should be used where writes to this peripheral register do not lead to memory
/// unsafety.
///
/// Access to this register must be synchronised; if multiple threads (or the main thread and an
/// interrupt service routine) are accessing it simultaneously you may encounter data races.
pub struct WORegister<T> {
register: UnsafeCell<T>,
}
impl<T: Copy> WORegister<T> {
/// Writes a new value to the register.
#[inline(always)]
pub fn write(&self, val: T) {
unsafe { ::core::ptr::write_volatile(self.register.get(), val) }
}
}
/// A write-only register of type T, where write access is unsafe.
///
/// Contains one value of type T and provides a volatile write function to it.
///
/// # Safety
/// This register should be used where reads and writes to this peripheral may invoke
/// undefined behaviour or memory unsafety.
///
/// Access to this register must be synchronised; if multiple threads (or the main thread and an
/// interrupt service routine) are accessing it simultaneously you may encounter data races.
pub struct UnsafeWORegister<T> {
register: UnsafeCell<T>,
}
impl<T: Copy> UnsafeWORegister<T> {
/// Writes a new value to the register.
#[inline(always)]
pub unsafe fn write(&self, val: T) {
::core::ptr::write_volatile(self.register.get(), val)
}
}
/// Write to a RWRegister or UnsafeRWRegister.
///
/// # Examples
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // Safely acquire the peripheral instance (will panic if already acquired)
/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
///
/// // Write some value to the register.
/// write_reg!(stm32ral::gpio, gpioa, ODR, 1<<3);
///
/// // Write values to specific fields. Unspecified fields are written to 0.
/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
///
/// // Unsafe access without requiring you to first `take()` the instance
/// unsafe { write_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
/// # }
/// ```
///
/// # Usage
/// Like `modify_reg!`, this macro can be used in two ways, either with a single value to write to
/// the whole register, or with multiple fields each with their own value.
///
/// In both cases, the first arguments are:
/// * the path to the peripheral module: `stm32ral::gpio`,
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
/// * the register you wish you access: `MODER` (a field on the `RegisterBlock`).
///
/// In the single-value usage, the final argument is just the value to write:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Turn on PA3 (and turn everything else off).
/// write_reg!(stm32ral::gpio, gpioa, ODR, 1<<3);
/// # }
/// ```
///
/// Otherwise, the remaining arguments are each `Field: Value` pairs:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // Set PA3 to Output, PA4 to Analog, and everything else to 0 (which is Input).
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: 0b01, MODER4: 0b11);
/// # }
/// ```
/// For fields with annotated values, you can also specify a named value:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // As above, but with named values.
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
/// # }
/// ```
///
/// This macro expands to calling `(*$instance).$register.write(value)`,
/// where in the second usage, the value is computed as the bitwise OR of
/// each field value, which are masked and shifted appropriately for the given field.
/// The named values are brought into scope by `use $peripheral::$register::$field::*` for
/// each field. The same constants could just be specified manually:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // As above, but being explicit about named values.
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: stm32ral::gpio::MODER::MODER3::RW::Output,
/// MODER4: stm32ral::gpio::MODER::MODER4::RW::Analog);
/// # }
/// ```
///
/// The fully expanded form is equivalent to:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // As above, but expanded.
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// (*gpioa).MODER.write(
/// ((stm32ral::gpio::MODER::MODER3::RW::Output << stm32ral::gpio::MODER::MODER3::offset)
/// & stm32ral::gpio::MODER::MODER3::mask)
/// |
/// ((stm32ral::gpio::MODER::MODER4::RW::Analog << stm32ral::gpio::MODER::MODER4::offset)
/// & stm32ral::gpio::MODER::MODER4::mask)
/// );
/// # }
/// ```
///
/// # Safety
/// This macro will require an unsafe function or block when used with an UnsafeRWRegister,
/// but not if used with RWRegister.
///
/// When run in an unsafe context, peripheral instances are directly accessible without requiring
/// having called `take()` beforehand:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// unsafe { write_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
/// # }
/// ```
/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
/// and the macro brings such constants into scope and then dereferences the provided reference.
#[macro_export]
#[doc(hidden)]
macro_rules! write_reg {
( $periph:path, $instance:expr, $reg:ident, $( $field:ident : $value:expr ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
#[allow(unused_imports)]
(*$instance).$reg.write(
$({ use $periph::{$reg::$field::{mask, offset, W::*, RW::*}}; ($value << offset) & mask }) | *
);
}};
( $periph:path, $instance:expr, $reg:ident, $value:expr ) => {{
#[allow(unused_imports)]
use $periph::{*};
(*$instance).$reg.write($value);
}};
}
/// Modify a RWRegister or UnsafeRWRegister.
///
/// # Examples
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // Safely acquire the peripheral instance (will panic if already acquired)
/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
///
/// // Update the register to ensure bit 3 is set.
/// modify_reg!(stm32ral::gpio, gpioa, ODR, |reg| reg | (1<<3));
///
/// // Write values to specific fields. Unspecified fields are left unchanged.
/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
///
/// // Unsafe access without requiring you to first `take()` the instance
/// unsafe { modify_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
/// # }
/// ```
///
/// # Usage
/// Like `write_reg!`, this macro can be used in two ways, either with a modification of the entire
/// register, or by specifying which fields to change and what value to change them to.
///
/// In both cases, the first arguments are:
/// * the path to the peripheral module: `stm32ral::gpio`,
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
/// * the register you wish you access: `MODER` (a field on the `RegisterBlock`).
///
/// In the whole-register usage, the final argument is a closure that accepts the current value
/// of the register and returns the new value to write:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Turn on PA3 without affecting anything else.
/// modify_reg!(stm32ral::gpio, gpioa, ODR, |reg| reg | (1<<3));
/// # }
/// ```
///
/// Otherwise, the remaining arguments are `Field: Value` pairs:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Set PA3 to Output, PA4 to Analog, and leave everything else unchanged.
/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: 0b01, MODER4: 0b11);
/// # }
/// ```
///
/// For fields with annotated values, you can also specify a named value:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // As above, but with named values.
/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
/// # }
/// ```
///
/// This macro expands to calling `(*instance).register.write(value)`.
/// When called with a closure, `(*instance).register.read()` is called, the result
/// passed in to the closure, and the return value of the closure is used for `value`.
/// When called with `Field: Value` arguments, the current value is read and then masked
/// according to the specified fields, and then ORd with the OR of each field value,
/// each masked and shifted appropriately for the field. The named values are brought into scope
/// by `use peripheral::register::field::*` for each field. The same constants could just be
/// specified manually:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // As above, but being explicit about named values.
/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: stm32ral::gpio::MODER::MODER3::RW::Output,
/// MODER4: stm32ral::gpio::MODER::MODER4::RW::Analog);
/// # }
/// ```
///
/// The fully expanded form is equivalent to:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // As above, but expanded.
/// (*gpioa).MODER.write(
/// (
/// // First read the current value...
/// (*gpioa).MODER.read()
/// // Then AND it with an appropriate mask...
/// &
/// !( stm32ral::gpio::MODER::MODER3::mask | stm32ral::gpio::MODER::MODER4::mask )
/// )
/// // Then OR with each field value.
/// |
/// ((stm32ral::gpio::MODER::MODER3::RW::Output << stm32ral::gpio::MODER::MODER3::offset)
/// & stm32ral::gpio::MODER::MODER3::mask)
/// |
/// ((stm32ral::gpio::MODER::MODER4::RW::Analog << stm32ral::gpio::MODER::MODER3::offset)
/// & stm32ral::gpio::MODER::MODER3::mask)
/// );
/// # }
/// ```
///
/// # Safety
/// This macro will require an unsafe function or block when used with an UnsafeRWRegister,
/// but not if used with RWRegister.
///
/// When run in an unsafe context, peripheral instances are directly accessible without requiring
/// having called `take()` beforehand:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// unsafe { modify_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
/// # }
/// ```
/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
/// and the macro brings such constants into scope and then dereferences the provided reference.
#[macro_export]
#[doc(hidden)]
macro_rules! modify_reg {
( $periph:path, $instance:expr, $reg:ident, $( $field:ident : $value:expr ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
#[allow(unused_imports)]
(*$instance).$reg.write(
((*$instance).$reg.read() & !( $({ use $periph::{$reg::$field::mask}; mask }) | * ))
| $({ use $periph::{$reg::$field::{mask, offset, W::*, RW::*}}; ($value << offset) & mask }) | *);
}};
( $periph:path, $instance:expr, $reg:ident, $fn:expr ) => {{
#[allow(unused_imports)]
use $periph::{*};
(*$instance).$reg.write($fn((*$instance).$reg.read()));
}};
}
/// Read the value from a RORegister, RWRegister, UnsafeRORegister, or UnsafeRWRegister.
///
/// # Examples
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // Safely acquire the peripheral instance (will panic if already acquired)
/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
///
/// // Read the whole register.
/// let val = read_reg!(stm32ral::gpio, gpioa, IDR);
///
/// // Read one field from the register.
/// let val = read_reg!(stm32ral::gpio, gpioa, IDR, IDR2);
///
/// // Read multiple fields from the register.
/// let (val1, val2, val3) = read_reg!(stm32ral::gpio, gpioa, IDR, IDR0, IDR1, IDR2);
///
/// // Check if one field is equal to a specific value, with the field's named values in scope.
/// while read_reg!(stm32ral::gpio, gpioa, IDR, IDR2 == High) {}
///
/// // Unsafe access without requiring you to first `take()` the instance
/// let val = unsafe { read_reg!(stm32ral::gpio, GPIOA, IDR) };
/// # }
/// ```
///
/// # Usage
/// Like `write_reg!`, this macro can be used multiple ways, either reading the entire register or
/// reading a one or more fields from it and potentially performing a comparison with one field.
///
/// In all cases, the first arguments are:
/// * the path to the peripheral module: `stm32ral::gpio`,
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
/// * the register you wish to access: `IDR` (a field on the `RegisterBlock`).
///
/// In the whole-register usage, the macro simply returns the register's value:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Read the entire value of GPIOA.IDR into `val`.
/// let val = read_reg!(stm32ral::gpio, gpioa, IDR);
/// # }
/// ```
///
/// For reading individual fields, the macro masks and shifts appropriately:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Read just the value of the field GPIOA.IDR2 into `val`.
/// let val = read_reg!(stm32ral::gpio, gpioa, IDR, IDR2);
///
/// // As above, but expanded for exposition:
/// let val = ((*gpioa).IDR.read() & stm32ral::gpio::IDR::IDR2::mask)
/// >> stm32ral::gpio::IDR::IDR2::offset;
///
/// // Read multiple fields
/// let (val1, val2) = read_reg!(stm32ral::gpio, gpioa, IDR, IDR2, IDR3);
///
/// // As above, but expanded for exposition:
/// let (val1, val2) = { let val = (*gpioa).IDR.read();
/// ((val & stm32ral::gpio::IDR::IDR2::mask) >> stm32ral::gpio::IDR::IDR2::offset,
/// (val & stm32ral::gpio::IDR::IDR3::mask) >> stm32ral::gpio::IDR::IDR3::offset,
/// )};
/// # }
/// ```
///
/// For comparing a single field, the macro masks and shifts and then performs the comparison:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// # let rcc = stm32ral::rcc::RCC::take().unwrap();
/// // Loop while PA2 is High.
/// while read_reg!(stm32ral::gpio, gpioa, IDR, IDR2 == High) {}
///
/// // Only proceed if the clock is not the HSI.
/// if read_reg!(stm32ral::rcc, rcc, CFGR, SWS != HSI) { }
///
/// // Equivalent expansion:
/// if (((*rcc).CFGR.read() & stm32ral::rcc::CFGR::SWS::mask)
/// >> stm32ral::rcc::CFGR::SWS::offset) != stm32ral::rcc::CFGR::SWS::R::HSI { }
/// # }
/// ```
///
/// # Safety
/// This macro will require an unsafe function or block when used with an UnsafeRWRegister or
/// UnsafeRORegister, but not if used with RWRegister, or RORegister.
///
/// When run in an unsafe context, peripheral instances are directly accessible without requiring
/// having called `take()` beforehand:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// let val = unsafe { read_reg!(stm32ral::gpio, GPIOA, MODER) };
/// # }
/// ```
/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
/// and the macro brings such constants into scope and then dereferences the provided reference.
#[macro_export]
#[doc(hidden)]
macro_rules! read_reg {
( $periph:path, $instance:expr, $reg:ident, $( $field:ident ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
let val = ((*$instance).$reg.read());
( $({
#[allow(unused_imports)]
use $periph::{$reg::$field::{mask, offset, R::*, RW::*}};
(val & mask) >> offset
}) , *)
}};
( $periph:path, $instance:expr, $reg:ident, $field:ident $($cmp:tt)* ) => {{
#[allow(unused_imports)]
use $periph::{*};
#[allow(unused_imports)]
use $periph::{$reg::$field::{mask, offset, R::*, RW::*}};
(((*$instance).$reg.read() & mask) >> offset) $($cmp)*
}};
( $periph:path, $instance:expr, $reg:ident ) => {{
#[allow(unused_imports)]
use $periph::{*};
((*$instance).$reg.read())
}};
}
/// Reset a RWRegister, UnsafeRWRegister, WORegister, or UnsafeWORegister to its reset value.
///
/// # Examples
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// // Safely acquire the peripheral instance (will panic if already acquired)
/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
///
/// // Reset PA14 and PA15 to their reset state
/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER, MODER14, MODER15);
///
/// // Reset the entire GPIOA.MODER to its reset state
/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER);
/// # }
/// ```
///
/// # Usage
/// Like `write_reg!`, this macro can be used in two ways, either resetting the entire register
/// or just resetting specific fields within in. The register or fields are written with their
/// reset values.
///
/// In both cases, the first arguments are:
/// * the path to the peripheral module: `stm32ral::gpio`,
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
/// * the module for the instance of that peripheral: `GPIOA`,
/// * the register you wish to access: `MODER` (a field on the `RegisterBlock`).
///
/// In the whole-register usage, that's it:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Reset the entire GPIOA.MODER
/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER);
/// # }
/// ```
///
/// Otherwise, the remaining arguments are each field names:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
/// // Reset the JTAG pins
/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER, MODER13, MODER14, MODER15);
/// reset_reg!(stm32ral::gpio, gpioa, GPIOB, MODER, MODER3, MODER4);
/// # }
/// ```
///
/// The second form is only available to RWRegister and UnsafeRWRegister, since `.read()` is
/// not available for WORegister and UnsafeWORegister.
///
/// This macro expands to calling `(*$instance).$register.write(value)`, where
/// `value` is either the register's reset value, or the current read value of the register
/// masked appropriately and combined with the reset value for each field.
///
/// # Safety
/// This macro will require an unsafe function or block when used with an UnsafeRWRegister or
/// UnsafeRORegister, but not if used with RWRegister or RORegister.
///
/// When run in an unsafe context, peripheral instances are directly accessible without requiring
/// having called `take()` beforehand:
/// ```rust,no_run
/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
/// unsafe { reset_reg!(stm32ral::gpio, GPIOA, GPIOA, MODER) };
/// # }
/// ```
/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
/// and the macro brings such constants into scope and then dereferences the provided reference.
///
/// Note that the second argument is a `*const` and the third is a path; despite both being written
/// `GPIOA` they are not the same thing.
#[macro_export]
#[doc(hidden)]
macro_rules! reset_reg {
( $periph:path, $instance:expr, $instancemod:path, $reg:ident, $( $field:ident ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
use $periph::{$instancemod::{reset}};
#[allow(unused_imports)]
(*$instance).$reg.write({
let resetmask: u32 = $({ use $periph::{$reg::$field::mask}; mask }) | *;
((*$instance).$reg.read() & !resetmask) | (reset.$reg & resetmask)
});
}};
( $periph:path, $instance:expr, $instancemod:path, $reg:ident ) => {{
#[allow(unused_imports)]
use $periph::{*};
use $periph::{$instancemod::{reset}};
(*$instance).$reg.write(reset.$reg);
}};
}
@@ -0,0 +1,7 @@
pub use super::instances::otg_fs_device_f401_f405_f407_f411_f427_f429 as otg_fs_device;
pub use super::instances::otg_fs_global_f401_f405_f407_f411_f427_f429 as otg_fs_global;
pub use super::instances::otg_fs_host;
pub use super::instances::otg_hs_device;
pub use super::instances::otg_hs_global;
pub use super::instances::otg_hs_host;
pub use super::instances::otg_s_pwrclk;
+92
View File
@@ -0,0 +1,92 @@
//! Target-specific definitions
use vcell::VolatileCell;
use crate::ral::register::RWRegister;
use crate::ral::{
endpoint0_out, endpoint_in, endpoint_out, otg_device, otg_global, otg_global_dieptxfx,
otg_pwrclk,
};
use crate::UsbPeripheral;
pub fn fifo_write(usb: UsbRegisters, channel: impl Into<usize>, mut buf: &[u8]) {
let fifo = usb.fifo(channel.into());
while buf.len() >= 4 {
let mut u32_bytes = [0u8; 4];
u32_bytes.copy_from_slice(&buf[..4]);
buf = &buf[4..];
fifo.write(u32::from_ne_bytes(u32_bytes));
}
if !buf.is_empty() {
let mut u32_bytes = [0u8; 4];
u32_bytes[..buf.len()].copy_from_slice(buf);
fifo.write(u32::from_ne_bytes(u32_bytes));
}
}
pub fn fifo_read_into(usb: UsbRegisters, buf: &[VolatileCell<u32>]) {
let fifo = usb.fifo(0);
for p in buf {
let word = fifo.read();
p.set(word);
}
}
/// Wrapper around device-specific peripheral that provides unified register interface
#[derive(Copy, Clone)]
pub struct UsbRegisters(usize);
impl UsbRegisters {
#[inline(always)]
pub fn new<USB: UsbPeripheral>() -> Self {
Self(USB::REGISTERS as usize)
}
#[inline(always)]
pub fn global(&self) -> &'static otg_global::RegisterBlock {
unsafe { &*(self.0 as *const _) }
}
#[inline(always)]
pub fn device(&self) -> &'static otg_device::RegisterBlock {
unsafe { &*((self.0 + 0x800) as *const _) }
}
#[inline(always)]
pub fn pwrclk(&self) -> &'static otg_pwrclk::RegisterBlock {
unsafe { &*((self.0 + 0xe00) as *const _) }
}
#[inline(always)]
pub fn fifo(&self, channel: usize) -> &'static RWRegister<u32> {
assert!(channel <= 15);
let address = self.0 + 0x1000 + channel * 0x1000;
unsafe { &*(address as *const RWRegister<u32>) }
}
#[inline(always)]
pub fn dieptxfx(&self, index: usize) -> &'static otg_global_dieptxfx::RegisterBlock {
let address = self.0 + 0x100 + 4 * index;
unsafe { &*(address as *const _) }
}
#[inline(always)]
pub fn endpoint_in(&self, index: usize) -> &'static endpoint_in::RegisterBlock {
let address = self.0 + 0x900 + 0x20 * index;
unsafe { &*(address as *const _) }
}
#[inline(always)]
pub fn endpoint0_out(&self) -> &'static endpoint0_out::RegisterBlock {
let address = self.0 + 0xb00;
unsafe { &*(address as *const _) }
}
#[inline(always)]
pub fn endpoint_out(&self, index: usize) -> &'static endpoint_out::RegisterBlock {
let address = self.0 + 0xb00 + 0x20 * index;
unsafe { &*(address as *const _) }
}
}
+382
View File
@@ -0,0 +1,382 @@
extern crate std;
use core::cell::UnsafeCell;
use std::{
boxed::Box,
sync::{Arc, Mutex},
vec::Vec,
};
use usb_device::{
bus::{PollResult, UsbBus as _},
device::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
endpoint::{EndpointIn, EndpointOut},
UsbError,
};
use crate::{UsbBus, UsbPeripheral};
const AHB_IDLE: u32 = 1 << 31;
const CORE_RESET: u32 = 1;
const RX_FLUSH: u32 = 1 << 4;
const TX_FLUSH: u32 = 1 << 5;
const GAHBCFG: usize = 0x08;
const GUSBCFG: usize = 0x0c;
const GRSTCTL: usize = 0x10;
const GINTSTS: usize = 0x14;
const GRXSTSR: usize = 0x1c;
const DCFG: usize = 0x800;
const DCTL: usize = 0x804;
const IN_SIZE: usize = 0x910;
struct Registers(UnsafeCell<[u32; 0x3000 / 4]>);
// Tests hold TEST_LOCK for the complete register/peripheral lifetime in use.
unsafe impl Sync for Registers {}
static REGISTERS: Registers = Registers(UnsafeCell::new([0; 0x3000 / 4]));
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn read(offset: usize) -> u32 {
// Only aligned offsets in the allocated register storage are used.
unsafe {
REGISTERS
.0
.get()
.cast::<u32>()
.add(offset / 4)
.read_volatile()
}
}
fn write(offset: usize, value: u32) {
// The emulated peripheral and driver access the same interior-mutable words.
unsafe {
REGISTERS
.0
.get()
.cast::<u32>()
.add(offset / 4)
.write_volatile(value)
}
}
fn snapshot() -> Vec<u32> {
(0..0x3000).step_by(4).map(read).collect()
}
#[derive(Default)]
struct Clock {
now: u64,
step: u64,
calls: u32,
blocked: u32,
second_idle: bool,
regress: bool,
aborted: u32,
hook: Option<Box<dyn FnOnce() + Send>>,
}
struct Peripheral(Arc<Mutex<Clock>>);
// This fixture is serialized and supplies the same aligned backing for all views.
unsafe impl UsbPeripheral for Peripheral {
const REGISTERS: *const () = REGISTERS.0.get().cast();
const HIGH_SPEED: bool = false;
const FIFO_DEPTH_WORDS: usize = 320;
const ENDPOINT_COUNT: usize = 6;
const WAIT_TIMEOUT_US: u32 = 5_000;
const WAIT_POLL_LIMIT: u32 = 32;
fn enable() {}
fn ahb_frequency_hz(&self) -> u32 {
216_000_000
}
fn now_us(&self) -> u64 {
let (now, hook) = {
let mut clock = self.0.lock().unwrap();
clock.calls += 1;
clock.now = if clock.regress {
clock.now.saturating_sub(1)
} else {
clock.now + clock.step
};
let mut reset = read(GRSTCTL);
if clock.blocked & AHB_IDLE != 0 || clock.second_idle && read(GUSBCFG) != 0 {
reset &= !AHB_IDLE;
}
reset &= !(CORE_RESET | RX_FLUSH | TX_FLUSH) | clock.blocked;
write(GRSTCTL, reset);
(clock.now, clock.hook.take())
};
if let Some(hook) = hook {
hook();
}
now
}
fn abort(&self) {
let hook = {
let mut clock = self.0.lock().unwrap();
clock.aborted += 1;
clock.hook.take()
};
if let Some(hook) = hook {
hook();
}
}
}
type Bus = UsbBus<Peripheral>;
struct Fixture {
device: UsbDevice<'static, Bus>,
input: EndpointIn<'static, Bus>,
output: EndpointOut<'static, Bus>,
clock: Arc<Mutex<Clock>>,
}
impl Fixture {
fn new(blocked: u32, second_idle: bool) -> Self {
for offset in (0..0x3000).step_by(4) {
write(offset, 0);
}
write(GRSTCTL, AHB_IDLE);
write(0x40, 0x2000); // F722 core identifier
let clock = Arc::new(Mutex::new(Clock {
step: 1_000,
blocked,
second_idle,
..Clock::default()
}));
let memory = Box::leak(Box::new([0; 32]));
let allocator = Box::leak(Box::new(UsbBus::new(Peripheral(clock.clone()), memory)));
let input = allocator.bulk(64);
let output = allocator.bulk(64);
let device = UsbDeviceBuilder::new(allocator, UsbVidPid(0x1209, 1))
.max_packet_size_0(64)
.unwrap()
.build();
write(GINTSTS, 0); // backing storage does not emulate W1C
Self {
device,
input,
output,
clock,
}
}
fn bus(&self) -> &Bus {
self.device.bus()
}
fn block(&self, bits: u32) {
self.clock.lock().unwrap().blocked = bits;
}
fn offline(&self) {
assert!(self.bus().is_offline());
assert_eq!(self.clock.lock().unwrap().aborted, 1);
let before = snapshot();
self.bus().reset();
self.bus().set_device_address(7);
self.bus().set_stalled(self.input.address(), true);
self.bus().suspend();
self.bus().resume();
self.bus().disable();
assert!(self.bus().is_stalled(self.input.address()));
assert!(matches!(self.bus().poll(), PollResult::None));
assert_eq!(self.input.write(&[1]), Err(UsbError::InvalidState));
assert_eq!(self.output.read(&mut [0; 64]), Err(UsbError::InvalidState));
assert_eq!(snapshot(), before);
assert_eq!(self.clock.lock().unwrap().aborted, 1);
}
}
#[test]
fn register_waits() {
let _test = TEST_LOCK.lock().unwrap();
for (blocked, second_idle) in [(AHB_IDLE, false), (0, true), (CORE_RESET, false)] {
let fixture = Fixture::new(blocked, second_idle);
fixture.offline();
assert_eq!(read(GAHBCFG) & 1, 0);
}
for blocked in [RX_FLUSH, TX_FLUSH, RX_FLUSH | TX_FLUSH] {
let fixture = Fixture::new(0, false);
fixture.block(blocked);
write(DCFG, 0x7f << 4);
fixture.bus().reset();
fixture.offline();
assert_eq!(read(DCFG), 0x7f << 4); // reset never continued after failure
assert_eq!(read(GAHBCFG) & 1, 0);
}
let fixture = Fixture::new(0, false);
fixture.block(RX_FLUSH);
write(GINTSTS, 1 << 12); // host reset
assert!(matches!(fixture.bus().poll(), PollResult::None));
fixture.offline();
let fixture = Fixture::new(0, false);
fixture.block(TX_FLUSH);
write(GINTSTS, 1 << 4); // RX FIFO level
write(GRXSTSR, 0x06 << 17); // SETUP for EP0
write(IN_SIZE, 1 << 19); // pending IN packet
assert!(matches!(fixture.bus().poll(), PollResult::None));
fixture.offline();
}
#[test]
fn healthy_reset_and_packets() {
let _test = TEST_LOCK.lock().unwrap();
let fixture = Fixture::new(0, false);
assert!(!fixture.bus().is_offline());
assert_eq!(read(GAHBCFG) & 1, 1);
fixture.bus().set_device_address(7);
fixture.bus().reset();
assert_eq!(read(DCFG) & (0x7f << 4), 0);
assert_eq!(read(GAHBCFG) & 1, 1);
assert_eq!(fixture.clock.lock().unwrap().aborted, 0);
write(0x900 + fixture.input.address().index() * 0x20 + 0x18, 16);
assert_eq!(fixture.input.write(&[9, 8, 7, 6]), Ok(4));
assert_eq!(
read(0x1000 + fixture.input.address().index() * 0x1000),
0x0607_0809
);
assert_eq!(fixture.input.write(&[1]), Err(UsbError::WouldBlock));
write(GINTSTS, (1 << 12) | (1 << 13));
write(0x808, 0b11 << 1); // Full-speed enumeration
assert!(matches!(fixture.bus().poll(), PollResult::Reset));
fixture.bus().reset();
assert!(!fixture.bus().is_offline());
write(GINTSTS, 1 << 4);
write(GRXSTSR, (0x06 << 17) | (8 << 4));
write(IN_SIZE, 1 << 19);
assert!(matches!(
fixture.bus().poll(),
PollResult::Data { ep_setup: 1, .. }
));
assert!(!fixture.bus().is_offline());
assert_eq!(fixture.bus().read(0u8.into(), &mut [0; 8]), Ok(8));
write(GINTSTS, 1 << 4);
write(
GRXSTSR,
(0x02 << 17) | (4 << 4) | fixture.output.address().index() as u32,
);
write(0x1000, 0x0403_0201);
assert!(matches!(fixture.bus().poll(), PollResult::Data { .. }));
let mut packet = [0; 64];
assert_eq!(fixture.output.read(&mut packet), Ok(4));
assert_eq!(&packet[..4], &[1, 2, 3, 4]);
write(GINTSTS, 0);
write(GAHBCFG, 0);
assert!(matches!(fixture.bus().poll(), PollResult::None));
assert_eq!(read(GAHBCFG), 0); // preserve an already masked USB source
}
#[test]
fn broken_clock_and_contention() {
let _test = TEST_LOCK.lock().unwrap();
for regress in [false, true] {
let fixture = Fixture::new(0, false);
fixture.block(RX_FLUSH);
let before = {
let mut clock = fixture.clock.lock().unwrap();
clock.step = 0;
clock.regress = regress;
clock.calls
};
fixture.bus().reset();
fixture.offline();
assert!(
fixture.clock.lock().unwrap().calls - before <= 2 * (Peripheral::WAIT_POLL_LIMIT + 1)
);
}
for mutation in [false, true] {
let fixture = Box::leak(Box::new(Fixture::new(0, false)));
let bus = fixture.bus();
let input = fixture.input.address();
let output = fixture.output.address();
fixture.clock.lock().unwrap().hook = Some(Box::new(move || {
assert_eq!(read(GAHBCFG) & 1, 0);
assert_eq!(bus.write(input, &[1]), Err(UsbError::WouldBlock));
assert_eq!(bus.read(output, &mut [0; 64]), Err(UsbError::WouldBlock));
assert!(matches!(bus.poll(), PollResult::None));
if mutation {
bus.set_device_address(42);
} else {
bus.disable();
}
}));
fixture.bus().reset();
fixture.offline();
assert_eq!(read(GAHBCFG) & 1, 0);
}
}
fn other_critical_section() {
let (send, recv) = std::sync::mpsc::channel();
let worker = std::thread::spawn(move || critical_section::with(|_| send.send(()).unwrap()));
recv.recv_timeout(std::time::Duration::from_secs(1))
.expect("callback holds global critical section");
worker.join().unwrap();
}
#[test]
fn wait_and_abort_allow_other_critical_sections() {
let _test = TEST_LOCK.lock().unwrap();
let fixture = Fixture::new(0, false);
fixture.clock.lock().unwrap().hook = Some(Box::new(other_critical_section));
fixture.bus().reset();
assert!(!fixture.bus().is_offline());
assert_eq!(read(DCTL) & (1 << 1), 0);
fixture.clock.lock().unwrap().hook = Some(Box::new(other_critical_section));
fixture.bus().disable();
fixture.offline();
}
struct Delay<F>(F);
impl<F: FnMut()> embedded_hal::blocking::delay::DelayMs<u32> for Delay<F> {
fn delay_ms(&mut self, _ms: u32) {
(self.0)();
}
}
#[test]
fn force_reset_failure() {
let _test = TEST_LOCK.lock().unwrap();
for unwind in [false, true] {
let fixture = Fixture::new(0, false);
let mut delay = Delay(|| {
if unwind {
panic!("delay failed");
}
fixture.bus().disable();
});
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
fixture.bus().force_reset(&mut delay)
}));
if unwind {
assert!(result.is_err());
} else {
assert_eq!(result.unwrap(), Err(UsbError::InvalidState));
}
fixture.offline();
assert_ne!(read(DCTL) & (1 << 1), 0); // Never reconnected after the failed delay.
assert_eq!(read(GAHBCFG) & 1, 0);
}
}
#[test]
fn flush_requires_idle() {
let _test = TEST_LOCK.lock().unwrap();
for path in 0..3 {
let fixture = Fixture::new(0, false);
fixture.block(AHB_IDLE);
match path {
0 => fixture.bus().reset(),
1 => {
write(GINTSTS, 1 << 12);
assert!(matches!(fixture.bus().poll(), PollResult::None));
}
_ => {
write(GINTSTS, 1 << 4);
write(GRXSTSR, 0x06 << 17);
write(IN_SIZE, 1 << 19);
assert!(matches!(fixture.bus().poll(), PollResult::None));
}
}
fixture.offline();
assert_eq!(read(GRSTCTL) & (RX_FLUSH | TX_FLUSH), 0);
}
}
+41
View File
@@ -0,0 +1,41 @@
use usb_device::endpoint::{EndpointAddress, EndpointType};
/// USB endpoint descriptor information.
pub struct EndpointDescriptor {
/// Endpoint address.
pub address: EndpointAddress,
/// Endpoint transfer type.
pub ep_type: EndpointType,
/// Maximum packet size.
pub max_packet_size: u16,
/// Poll interval for interrupt endpoints.
pub interval: u8,
}
/// Configuration for an endpoint allocation.
pub struct EndpointConfig {
/// The transfer type of the endpoint to be allocated.
pub ep_type: EndpointType,
/// Maximum packet size for the endpoint to be allocated.
pub max_packet_size: u16,
/// Poll interval for interrupt endpoints.
pub interval: u8,
/// Requests a specific endpoint number. Allocation shall fail if the number is not available.
pub number: Option<u8>,
/// Specifies that the endpoint is the "pair" of another endpoint.
///
/// If `ep` is an endpoint in the opposite direction, this means that the endpoint to be
/// allocated uses the same transfer type as `ep` but in the opposite direction in all alternate
/// settings for the interface.
///
/// If `ep` is an endpoint in the same direction, this means that the two endpoints belong to
/// different alternate settings for the interface and may never be enabled at the same time.
pub pair_of: Option<EndpointAddress>,
}