prep first release #2
+1
-1
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user