libs/kaptan-stm32f7: add USART1 DMA transmit route

This commit is contained in:
Onur Özkan committed 2026-07-19 22:40:04 +03:00
1 parent 182c86a766
commit e917650602
2 files changed
+253

No files matched your search

@@ -0,0 +1,35 @@
//! D-cache-disabled DMA memory policy for STM32F722 boards.
use kaptan_dma::{DmaReadableBuffer, DmaTxBufferFactory};
///
/// DMA transmit memory policy for a board with D-cache disabled.
///
/// The board's linker script must also place transfer frames in SRAM reachable
/// by the selected DMA controller. This policy does not support a cached board;
/// such a board requires a distinct policy that performs cache maintenance.
pub struct DcacheDisabledDmaMemory {
_private: (),
}
impl DcacheDisabledDmaMemory {
/// Declares that the selected board disabled D-cache before DMA setup.
///
/// # Safety
///
/// - D-cache must remain disabled for every CPU access to DMA-owned frames.
/// - The linker policy must locate those frames in DMA-visible SRAM.
pub const unsafe fn new() -> Self {
Self { _private: () }
}
}
impl DmaTxBufferFactory for DcacheDisabledDmaMemory {
type Error = core::convert::Infallible;
fn prepare<'a>(&mut self, bytes: &'a [u8]) -> Result<DmaReadableBuffer<'a>, Self::Error> {
// SAFETY: `Self` can exist only after the board proved its D-cache and
// linker-memory policy. The runtime retains frame ownership until a
// terminal DMA outcome or successful recovery.
Ok(unsafe { DmaReadableBuffer::new_unchecked(bytes) })
}
}
@@ -0,0 +1,218 @@
//! Fixed STM32F722 DMA route for USART1 transmit.
//!
//! USART1 TX uses DMA2 stream 7, channel 4 on STM32F722. The route is a type,
//! rather than board-provided integer parameters, so invalid request mappings
//! cannot be selected at construction.
use kaptan_dma::{DmaReadableBuffer, DmaTxEngine, TxCompletion};
use stm32f7::stm32f722;
use super::Usart1Tx;
/// Owns the fixed STM32F722 USART1 TX DMA route.
pub struct Usart1TxDma {
usart: Usart1Tx,
}
/// Failure to start one DMA frame transfer.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StartError {
/// DMA cannot transfer an empty buffer or one exceeding the NDTR limit.
InvalidBufferLength,
/// DMA2 stream 7 still owns an earlier transfer.
Busy,
}
impl Usart1TxDma {
/// Enables DMA2 and connects USART1 TX to its fixed request route.
pub fn new(rcc: &stm32f722::RCC, usart: Usart1Tx) -> Self {
rcc.ahb1enr().modify(|_, write| write.dma2en().set_bit());
usart.enable_dma_transmit();
Self { usart }
}
/// Starts one memory-to-USART DMA transfer.
///
/// # Ownership
///
/// The caller must retain `buffer`'s backing storage until
/// [`Self::acknowledge_interrupt`] returns a terminal result.
/// - The board must bind `DMA2_STREAM7`, call `acknowledge_interrupt` from
/// that interrupt, and defer lifecycle handling to its single owner.
pub fn start(&mut self, buffer: DmaReadableBuffer<'_>) -> Result<(), StartError> {
let bytes = buffer.bytes();
if bytes.is_empty() || bytes.len() > u16::MAX as usize {
return Err(StartError::InvalidBufferLength);
}
if self.is_active() {
return Err(StartError::Busy);
}
self.clear_interrupt_flags();
self.configure_stream(bytes);
self.stream().cr().modify(|_, write| write.en().enabled());
Ok(())
}
/// Acknowledges a DMA2 Stream7 terminal IRQ without accessing engine state.
pub fn acknowledge_interrupt() -> TxCompletion {
// SAFETY: this handler accesses only the fixed DMA2 Stream7 route.
let controller = unsafe { &*stm32f722::DMA2::ptr() };
let status = controller.hisr().read();
let completion = DmaTerminalStatus {
transfer_complete: status.tcif7().is_complete(),
transfer_error: status.teif7().is_error(),
direct_mode_error: status.dmeif7().is_error(),
fifo_error: status.feif7().is_error(),
}
.classify();
if completion == TxCompletion::NotPending {
return completion;
}
// A normal-mode STM32 DMA stream clears EN at terminal completion.
// Request disable without polling: a later start observes EN and
// refuses ownership rather than blocking an interrupt handler.
controller
.st(7)
.cr()
.modify(|_, write| write.en().disabled());
controller.hifcr().write(|write| {
write
.ctcif7()
.clear()
.chtif7()
.clear()
.cteif7()
.clear()
.cdmeif7()
.clear()
.cfeif7()
.clear()
});
completion
}
/// Returns whether DMA2 stream 7 owns a transfer.
pub fn is_active(&self) -> bool {
self.stream().cr().read().en().is_enabled()
}
fn configure_stream(&mut self, buffer: &[u8]) {
self.stream().cr().write(|write| {
// SAFETY: Channel 4 and these transfer fields are the documented
// USART1 TX mapping for DMA2 stream 7 on STM32F722.
unsafe {
write
.chsel()
.bits(4)
.dir()
.memory_to_peripheral()
.pl()
.very_high()
.minc()
.incremented()
.pinc()
.fixed()
.msize()
.bits8()
.psize()
.bits8()
.circ()
.disabled()
.tcie()
.enabled()
.teie()
.enabled()
.dmeie()
.enabled()
}
});
self.stream().ndtr().write(|write| {
// SAFETY: `start` checked the slice length against the NDTR limit.
unsafe { write.bits(buffer.len() as u32) }
});
self.stream().par().write(|write| {
// SAFETY: This is the memory-mapped USART1 transmit-data register.
unsafe { write.bits(self.usart.transmit_data_register_address()) }
});
self.stream().m0ar().write(|write| {
// SAFETY: The caller of `start` upholds the buffer lifetime contract.
unsafe { write.bits(buffer.as_ptr() as u32) }
});
}
fn stream(&self) -> &stm32f722::dma2::ST {
self.controller().st(7)
}
fn clear_interrupt_flags(&self) {
self.controller().hifcr().write(|write| {
write
.ctcif7()
.clear()
.chtif7()
.clear()
.cteif7()
.clear()
.cdmeif7()
.clear()
.cfeif7()
.clear()
});
}
fn controller(&self) -> &stm32f722::dma2::RegisterBlock {
// SAFETY: SPI1 owns DMA2 initialization; USART1 TX is permanently
// assigned to the disjoint Stream7 route and accesses no other stream.
unsafe { &*stm32f722::DMA2::ptr() }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct DmaTerminalStatus {
transfer_complete: bool,
transfer_error: bool,
direct_mode_error: bool,
fifo_error: bool,
}
impl DmaTerminalStatus {
const fn classify(self) -> TxCompletion {
if self.transfer_error || self.direct_mode_error || self.fifo_error {
TxCompletion::TransferFault
} else if self.transfer_complete {
TxCompletion::Complete
} else {
TxCompletion::NotPending
}
}
}
impl DmaTxEngine for Usart1TxDma {
type StartError = StartError;
fn start(&mut self, buffer: DmaReadableBuffer<'_>) -> Result<(), Self::StartError> {
Self::start(self, buffer)
}
}
#[cfg(test)]
mod tests {
use super::DmaTerminalStatus;
use kaptan_dma::TxCompletion;
#[test]
fn terminal_error_has_precedence_over_completion() {
let status = DmaTerminalStatus {
transfer_complete: true,
transfer_error: false,
direct_mode_error: false,
fifo_error: true,
};
assert_eq!(status.classify(), TxCompletion::TransferFault);
}
}