diff --git a/CHANGELOG.md b/CHANGELOG.md index 53032f9..aa1b3da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] -# [v0.1.0] +# [v0.1.0] 2025-11-28 Initial release. diff --git a/Cargo.toml b/Cargo.toml index 9759aee..e4fdd27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,13 @@ name = "axi-uartlite" version = "0.1.0" description = "LogiCORE AXI UART Lite v2.0 driver" +author = ["Robin Mueller "] edition = "2024" license = "MIT OR Apache-2.0" [dependencies] derive-mmio = "0.6" -bitbybit = "1.3" +bitbybit = "1.4" arbitrary-int = "2" nb = "1" embedded-hal-nb = "1" diff --git a/justfile b/justfile index e88ac38..efcddd9 100644 --- a/justfile +++ b/justfile @@ -1,9 +1,12 @@ -all: check build embedded clippy fmt docs coverage +all: check build embedded clippy check-fmt docs clippy: cargo clippy -- -D warnings fmt: + cargo fmt --all + +check-fmt: cargo fmt --all -- --check check: @@ -20,10 +23,10 @@ build: cargo build docs: - RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc + RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --no-deps docs-html: - RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --open + RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --open --no-deps coverage: cargo llvm-cov nextest diff --git a/src/lib.rs b/src/lib.rs index 8e08c7c..f48cf5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ //! - `32-wakers` #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] +#![deny(missing_docs)] use core::convert::Infallible; use registers::Control; @@ -29,8 +30,10 @@ pub use rx::*; pub mod tx_async; pub use tx_async::*; +/// Maximum FIFO depth of the AXI UART Lite. pub const FIFO_DEPTH: usize = 16; +/// RX error structure. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct RxErrorsCounted { parity: u8, @@ -39,6 +42,7 @@ pub struct RxErrorsCounted { } impl RxErrorsCounted { + /// Create a new empty RX error counter. pub const fn new() -> Self { Self { parity: 0, @@ -47,23 +51,28 @@ impl RxErrorsCounted { } } + /// Parity error count. pub const fn parity(&self) -> u8 { self.parity } + /// Frame error count. pub const fn frame(&self) -> u8 { self.frame } + /// Overrun error count. pub const fn overrun(&self) -> u8 { self.overrun } + /// Some error has occurred. pub fn has_errors(&self) -> bool { self.parity > 0 || self.frame > 0 || self.overrun > 0 } } +/// AXI UART Lite peripheral driver. pub struct AxiUartlite { rx: Rx, tx: Tx, @@ -94,6 +103,7 @@ impl AxiUartlite { } } + /// Direct register access. #[inline(always)] pub const fn regs(&mut self) -> &mut registers::MmioRegisters<'static> { &mut self.tx.regs @@ -119,35 +129,39 @@ impl AxiUartlite { self.tx.write_fifo_unchecked(data); } + /// Read from the UART Lite. + /// + /// Offers a #[inline] pub fn read_fifo(&mut self) -> nb::Result { - let val = self.rx.read_fifo().unwrap(); + let val = self.rx.read_fifo()?; if let Some(errors) = self.rx.errors { self.handle_status_reg_errors(errors); } Ok(val) } + /// Read from the FIFO without checking the FIFO fill status. #[inline(always)] pub fn read_fifo_unchecked(&mut self) -> u8 { self.rx.read_fifo_unchecked() } - // TODO: Make this non-mut as soon as pure reads are available + /// Is the TX FIFO empty? #[inline(always)] - pub fn tx_fifo_empty(&mut self) -> bool { + pub fn tx_fifo_empty(&self) -> bool { self.tx.fifo_empty() } - // TODO: Make this non-mut as soon as pure reads are available + /// TX FIFO full status. #[inline(always)] - pub fn tx_fifo_full(&mut self) -> bool { + pub fn tx_fifo_full(&self) -> bool { self.tx.fifo_full() } - // TODO: Make this non-mut as soon as pure reads are available + /// RX FIFO has data. #[inline(always)] - pub fn rx_has_data(&mut self) -> bool { + pub fn rx_has_data(&self) -> bool { self.rx.has_data() } @@ -171,6 +185,7 @@ impl AxiUartlite { } } + /// Reset the RX FIFO. #[inline] pub fn reset_rx_fifo(&mut self) { self.regs().write_ctrl_reg( @@ -182,6 +197,7 @@ impl AxiUartlite { ); } + /// Reset the TX FIFO. #[inline] pub fn reset_tx_fifo(&mut self) { self.regs().write_ctrl_reg( @@ -193,11 +209,13 @@ impl AxiUartlite { ); } + /// Split the driver into [Tx] and [Rx] halves. #[inline] pub fn split(self) -> (Tx, Rx) { (self.tx, self.rx) } + /// Enable UART Lite interrupts. #[inline] pub fn enable_interrupt(&mut self) { self.regs().write_ctrl_reg( @@ -209,6 +227,7 @@ impl AxiUartlite { ); } + /// Disable UART Lite interrupts. #[inline] pub fn disable_interrupt(&mut self) { self.regs().write_ctrl_reg( diff --git a/src/registers.rs b/src/registers.rs index da4bd56..c9f5890 100644 --- a/src/registers.rs +++ b/src/registers.rs @@ -1,29 +1,43 @@ +//! # Raw register module + +/// RX FIFO register. #[bitbybit::bitfield(u32)] pub struct RxFifo { + /// Data which can be read. #[bits(0..=7, r)] pub data: u8, } +/// TX FIFO register. #[bitbybit::bitfield(u32)] pub struct TxFifo { + /// Data to be transmitted. #[bits(0..=7, w)] pub data: u8, } +/// Status register. #[bitbybit::bitfield(u32)] pub struct Status { + /// Parity error bit. #[bit(7, r)] pub parity_error: bool, + /// Frame error bit. #[bit(6, r)] pub frame_error: bool, + /// Overrun error bit. #[bit(5, r)] pub overrun_error: bool, + /// Interrupt enabled bit. #[bit(4, r)] pub intr_enabled: bool, + /// TX FIFO full. #[bit(3, r)] pub tx_fifo_full: bool, + /// TX FIFO empty. #[bit(2, r)] pub tx_fifo_empty: bool, + /// RX FIFO full. #[bit(1, r)] pub rx_fifo_full: bool, /// RX FIFO contains valid data. @@ -31,12 +45,16 @@ pub struct Status { pub rx_fifo_valid_data: bool, } +/// Control register. #[bitbybit::bitfield(u32, default = 0x0)] pub struct Control { + /// Enable interrupt bit. #[bit(4, w)] enable_interrupt: bool, + /// Reset RX FIFO. #[bit(1, w)] reset_rx_fifo: bool, + /// Reset TX FIFO. #[bit(0, w)] reset_tx_fifo: bool, } diff --git a/src/rx.rs b/src/rx.rs index 768aca4..1ed6c26 100644 --- a/src/rx.rs +++ b/src/rx.rs @@ -1,7 +1,9 @@ +//! # Receiver (RX) support module use core::convert::Infallible; use crate::registers::{self, Registers, Status}; +/// RX error structure which tracks if an error has occurred. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct RxErrors { parity: bool, @@ -10,6 +12,7 @@ pub struct RxErrors { } impl RxErrors { + /// Create a new empty RX error structure. pub const fn new() -> Self { Self { parity: false, @@ -18,23 +21,31 @@ impl RxErrors { } } + /// Parity error occurred. pub const fn parity(&self) -> bool { self.parity } + /// Frame error occurred. pub const fn frame(&self) -> bool { self.frame } + /// Overrun error occurred. pub const fn overrun(&self) -> bool { self.overrun } + /// Any error has occurred. pub const fn has_errors(&self) -> bool { self.parity || self.frame || self.overrun } } +/// AXI UARTLITE TX driver. +/// +/// Can be created by [super::AxiUartlite::split]ting a regular AXI UARTLITE structure or +/// by [Self::steal]ing it unsafely. pub struct Rx { pub(crate) regs: registers::MmioRegisters<'static>, pub(crate) errors: Option, @@ -62,6 +73,10 @@ impl Rx { } } + /// Read the RX FIFO. + /// + /// This functions offers a [nb::Result] based API and returns [nb::Error::WouldBlock] if there + /// is nothing to read. #[inline] pub fn read_fifo(&mut self) -> nb::Result { let status_reg = self.regs.read_stat_reg(); @@ -75,14 +90,15 @@ impl Rx { Ok(val) } + /// Read from the FIFO without checking the FIFO fill status. #[inline(always)] pub fn read_fifo_unchecked(&mut self) -> u8 { self.regs.read_rx_fifo().data() } - // TODO: Make this non-mut as soon as pure reads are available + /// Does the RX FIFO have valid data? #[inline(always)] - pub fn has_data(&mut self) -> bool { + pub fn has_data(&self) -> bool { self.regs.read_stat_reg().rx_fifo_valid_data() } @@ -112,6 +128,9 @@ impl Rx { self.read_whole_fifo(buf) } + /// Read and clear the last RX errors. + /// + /// Returns [None] if no errors have occured. pub fn read_and_clear_last_error(&mut self) -> Option { let errors = self.errors?; self.errors = None; @@ -154,6 +173,7 @@ impl embedded_io::Read for Rx { } } +/// Extract RX errors from the status register. pub const fn handle_status_reg_errors(status_reg: &Status) -> Option { let mut errors = RxErrors::new(); if status_reg.frame_error() { diff --git a/src/tx.rs b/src/tx.rs index 2cac4f5..a564089 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -1,3 +1,4 @@ +//! # Transmitter (TX) support module use core::convert::Infallible; use crate::{ @@ -5,6 +6,10 @@ use crate::{ registers::{self, Control, TxFifo}, }; +/// AXI UARTLITE TX driver. +/// +/// Can be created by [super::AxiUartlite::split]ting a regular AXI UARTLITE structure or +/// by [Self::steal]ing it unsafely. pub struct Tx { pub(crate) regs: registers::MmioRegisters<'static>, pub(crate) errors: Option, @@ -45,6 +50,7 @@ impl Tx { Ok(()) } + /// Reset the TX FIFO. #[inline] pub fn reset_fifo(&mut self) { let status = self.regs.read_stat_reg(); @@ -66,15 +72,15 @@ impl Tx { .write_tx_fifo(TxFifo::new_with_raw_value(data as u32)); } - // TODO: Make this non-mut as soon as pure reads are available + /// Is the TX FIFO empty? #[inline(always)] - pub fn fifo_empty(&mut self) -> bool { + pub fn fifo_empty(&self) -> bool { self.regs.read_stat_reg().tx_fifo_empty() } - // TODO: Make this non-mut as soon as pure reads are available + /// Is the TX FIFO full? #[inline(always)] - pub fn fifo_full(&mut self) -> bool { + pub fn fifo_full(&self) -> bool { self.regs.read_stat_reg().tx_fifo_full() } @@ -93,6 +99,7 @@ impl Tx { written } + /// Read and clear the last recorded RX errors. pub fn read_and_clear_last_error(&mut self) -> Option { let errors = self.errors?; self.errors = None; diff --git a/src/tx_async.rs b/src/tx_async.rs index 854cae3..eae1c5b 100644 --- a/src/tx_async.rs +++ b/src/tx_async.rs @@ -23,16 +23,22 @@ use raw_slice::RawBufSlice; use crate::{FIFO_DEPTH, Tx}; +/// 1 waker (default). #[cfg(feature = "1-waker")] pub const NUM_WAKERS: usize = 1; +/// 2 wakers #[cfg(feature = "2-wakers")] pub const NUM_WAKERS: usize = 2; +/// 4 wakers #[cfg(feature = "4-wakers")] pub const NUM_WAKERS: usize = 4; +/// 8 wakers #[cfg(feature = "8-wakers")] pub const NUM_WAKERS: usize = 8; +/// 16 wakers #[cfg(feature = "16-wakers")] pub const NUM_WAKERS: usize = 16; +/// 32 wakers #[cfg(feature = "32-wakers")] pub const NUM_WAKERS: usize = 32; static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS]; @@ -42,6 +48,7 @@ static TX_CONTEXTS: [Mutex>; NUM_WAKERS] = // critical section. static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS]; +/// Invalid waker index for [NUM_WAKERS]. #[derive(Debug, thiserror::Error)] #[error("invalid waker slot index: {0}")] pub struct InvalidWakerIndex(pub usize); @@ -50,8 +57,9 @@ pub struct InvalidWakerIndex(pub usize); /// UART peripheral. /// /// The user has to call this once in the interrupt handler responsible if the interrupt was -/// triggered by the UARTLite. The relevant [Tx] handle of the UARTLite and the waker slot used -/// for it must be passed as well. [Tx::steal] can be used to create the required handle. +/// triggered by the UARTLite using [TxAsync]. The relevant [Tx] handle of the UARTLite and the +/// waker slot used for it must be passed as well. [Tx::steal] can be used to create the required +/// handle. pub fn on_interrupt_tx(uartlite_tx: &mut Tx, waker_slot: usize) { if waker_slot >= NUM_WAKERS { return; @@ -100,6 +108,7 @@ pub fn on_interrupt_tx(uartlite_tx: &mut Tx, waker_slot: usize) { }); } +/// TX context structure. #[derive(Debug, Copy, Clone)] pub struct TxContext { progress: usize, @@ -108,6 +117,7 @@ pub struct TxContext { #[allow(clippy::new_without_default)] impl TxContext { + /// Create a new TX context structure. pub const fn new() -> Self { Self { progress: 0, @@ -116,11 +126,13 @@ impl TxContext { } } -pub struct TxFuture { +/// TX future structure. +pub struct TxFuture<'tx> { waker_idx: usize, + tx: &'tx mut TxAsync, } -impl TxFuture { +impl<'tx> TxFuture<'tx> { /// Create a new TX future which can be used for asynchronous TX operations. /// /// # Safety @@ -128,17 +140,17 @@ impl TxFuture { /// This function stores the raw pointer of the passed data slice. The user MUST ensure /// that the slice outlives the data structure. pub unsafe fn new( - tx: &mut Tx, + tx: &'tx mut TxAsync, waker_idx: usize, data: &[u8], - ) -> Result { + ) -> Result, InvalidWakerIndex> { TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed); - tx.reset_fifo(); + tx.tx.reset_fifo(); let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH); // We fill the FIFO with initial data. for data in data.iter().take(init_fill_count) { - tx.write_fifo_unchecked(*data); + tx.tx.write_fifo_unchecked(*data); } critical_section::with(|cs| { let context_ref = TX_CONTEXTS[waker_idx].borrow(cs); @@ -148,11 +160,11 @@ impl TxFuture { } context.progress = init_fill_count; }); - Ok(Self { waker_idx }) + Ok(Self { waker_idx, tx }) } } -impl Future for TxFuture { +impl Future for TxFuture<'_> { type Output = usize; fn poll( @@ -172,7 +184,7 @@ impl Future for TxFuture { } } -impl Drop for TxFuture { +impl Drop for TxFuture<'_> { fn drop(&mut self) { if !TX_DONE[self.waker_idx].load(core::sync::atomic::Ordering::Relaxed) { critical_section::with(|cs| { @@ -180,17 +192,21 @@ impl Drop for TxFuture { let mut context_mut = context_ref.borrow_mut(); context_mut.slice.set_null(); context_mut.progress = 0; + // We can not disable interrupts, might be active for RX as well. + self.tx.tx.reset_fifo(); }); } } } +/// Asynchronous TX structure. pub struct TxAsync { - tx: Tx, + pub(crate) tx: Tx, waker_idx: usize, } impl TxAsync { + /// Create a new asynchronous TX structure. pub fn new(tx: Tx, waker_idx: usize) -> Result { if waker_idx >= NUM_WAKERS { return Err(InvalidWakerIndex(waker_idx)); @@ -206,10 +222,11 @@ impl TxAsync { if buf.is_empty() { return 0; } - let fut = unsafe { TxFuture::new(&mut self.tx, self.waker_idx, buf).unwrap() }; + let fut = unsafe { TxFuture::new(self, self.waker_idx, buf).unwrap() }; fut.await } + /// Release the owned TX structure. pub fn release(self) -> Tx { self.tx }