libs/kaptan-stm32f7: add USART1 DMA transmit route
This commit is contained in:
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user