prep first release #2

Merged
muellerr merged 1 commits from prep-v0.1.0 into main 2025-11-28 10:20:56 +01:00
8 changed files with 116 additions and 31 deletions
+1 -1
View File
@@ -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.
+2 -1
View File
@@ -2,12 +2,13 @@
name = "axi-uartlite"
version = "0.1.0"
description = "LogiCORE AXI UART Lite v2.0 driver"
author = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
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"
+6 -3
View File
@@ -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
+26 -7
View File
@@ -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<u8, Infallible> {
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(
+18
View File
@@ -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,
}
+22 -2
View File
@@ -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<RxErrors>,
@@ -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<u8, Infallible> {
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<RxErrors> {
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<RxErrors> {
let mut errors = RxErrors::new();
if status_reg.frame_error() {
+11 -4
View File
@@ -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<RxErrors>,
@@ -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<RxErrors> {
let errors = self.errors?;
self.errors = None;
+30 -13
View File
@@ -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<RefCell<TxContext>>; 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<Self, InvalidWakerIndex> {
) -> Result<TxFuture<'tx>, 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<Self, InvalidWakerIndex> {
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
}