From df99db3895ff85ce95b64cc2704f1a4c1a925d5e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 28 Nov 2025 11:43:07 +0100 Subject: [PATCH] prep v0.1.0 --- .github/workflows/ci.yml | 65 +++++++++++++++++++++++ CHANGELOG.md | 16 ++++++ Cargo.toml | 7 ++- README.md | 28 ++++++++++ justfile | 35 +++++++++++++ src/lib.rs | 108 +++++++++++++++++++++++++++++---------- src/registers.rs | 65 ++++++++++++++++++----- src/rx.rs | 67 ++++++++++++++++++------ src/tx.rs | 31 +++++++---- src/tx_async.rs | 18 +++++-- 10 files changed, 368 insertions(+), 72 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 justfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..df6ae9a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: ci +on: [push, pull_request] + +jobs: + check: + name: Check build + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo check + + msrv: + name: Check MSRV + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.85.1 + - run: cargo build + + cross-check: + name: Check Cross-Compilation + runs-on: ubuntu-latest + strategy: + matrix: + target: + - armv7-unknown-linux-gnueabihf + - armv7a-none-eabi + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: "armv7-unknown-linux-gnueabihf, armv7a-none-eabi" + - run: cargo build --target=${{matrix.target}} + + fmt: + name: Check formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all -- --check + + docs: + name: Check Documentation Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - run: RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --no-deps + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - run: cargo clippy -- -D warnings diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0ed7393 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +Change Log +======= + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +# [unreleased] + +# [v0.1.0] 2025-11-28 + +Initial release. + +[unreleased]: https://egit.irs.uni-stuttgart.de/rust/axi-uart16550/compare/v0.1.1...HEAD +[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/axi-uart16550/tag/v0.1.0 diff --git a/Cargo.toml b/Cargo.toml index a0954b5..b0e528e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,17 @@ [package] name = "axi-uart16550" version = "0.1.0" +description = "AXI UART16550 IP core driver" +author = ["Robin Mueller "] +rust-version = "1.85.1" edition = "2024" +homepage = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550" +repository = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550" license = "MIT OR Apache-2.0" [dependencies] derive-mmio = "0.6" -bitbybit = "1.3" +bitbybit = "1.4" arbitrary-int = "2" nb = "1" libm = "0.2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb72bf1 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +[![Crates.io](https://img.shields.io/crates/v/axi-uart16550)](https://crates.io/crates/axi-uart16550) +[![docs.rs](https://img.shields.io/docsrs/axi-uart16550)](https://docs.rs/axi-uart16550) +[![ci](https://github.com/us-irs/axi-uartlite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/axi-uartlite/actions/workflows/ci.yml) + +AXI UART16550 driver +======== + +This is a native Rust driver for the +[AMD AXI UART16550 IP core](https://www.amd.com/de/products/adaptive-socs-and-fpgas/intellectual-property/axi_uart16550.html). + +# Core features + +- Basic driver which can be created with a given IP core base address and supports a basic + byte-level read and write API. +- Support for [`embedded-io`](https://docs.rs/embedded-io/latest/embedded_io/) and + [`embedded-io-async`](https://docs.rs/embedded-io-async/latest/embedded_io_async/) + +# Features + +If the asynchronous support for the TX side is used, the number of statically provided wakers +can be configured using the following features: + +- `1-waker` which is the default +- `2-wakers` +- `4-wakers` +- `8-wakers` +- `16-wakers` +- `32-wakers` diff --git a/justfile b/justfile new file mode 100644 index 0000000..ed3c18b --- /dev/null +++ b/justfile @@ -0,0 +1,35 @@ +all: check build embedded clippy check-fmt docs + +clippy: + cargo clippy -- -D warnings + +fmt: + cargo fmt --all + +check-fmt: + cargo fmt --all -- --check + +check: + cargo check + +embedded: + cargo build --target armv7a-none-eabi + +test: + cargo nextest r + cargo test --doc + +build: + cargo build + +docs: + 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 --no-deps + +coverage: + cargo llvm-cov nextest + +coverage-html: + cargo llvm-cov nextest --html --open diff --git a/src/lib.rs b/src/lib.rs index f9329bc..d18f919 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,26 @@ +//! # AMD AXI UART16550 driver +//! +//! This is a native Rust driver for the [AMD AXI UART16550](https://www.amd.com/de/products/adaptive-socs-and-fpgas/intellectual-property/axi_uart16550.html) +//! IP core. +//! +//! # Features +//! +//! If asynchronous TX operations are used, the number of wakers which defaults to 1 waker can +//! also be configured. The [tx_async] module provides more details on the meaning of this number. +//! +//! - `1-waker` which is also a `default` feature +//! - `2-wakers` +//! - `4-wakers` +//! - `8-wakers` +//! - `16-wakers` +//! - `32-wakers` #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny(missing_docs)] use core::convert::Infallible; -use registers::{Fcr, Ier, Lcr, RxFifoTrigger, StopBits, WordLen}; +use registers::{FifoControl, InterruptEnable, LineControl, RxFifoTrigger, StopBits, WordLen}; pub mod registers; pub mod tx; @@ -14,14 +32,20 @@ pub use tx_async::*; pub mod rx; pub use rx::*; +/// Maximum FIFO depth of the AXI UART16550. pub const FIFO_DEPTH: usize = 16; + +/// Default RX FIFO trigger level. pub const DEFAULT_RX_TRIGGER_LEVEL: RxFifoTrigger = RxFifoTrigger::EightBytes; +/// Clock configuration structure. #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct ClkConfig { +pub struct ClockConfig { + /// Divisor value. pub div: u16, } +/// Divisor is zero error. #[derive(Debug, thiserror::Error, PartialEq, Eq)] #[error("divisor is zero")] pub struct DivisorZeroError; @@ -45,21 +69,26 @@ pub fn calculate_error_rate_from_div( /// used clock is too large, or the baudrate is too slow for the used clock frequency. #[derive(Debug, thiserror::Error, PartialEq, Eq)] #[error("divisor too large")] -pub enum ClkConfigError { +pub enum ClockConfigError { + /// Divisor too large error. DivisorTooLargeError(u32), + /// Divisor is zero error. DivisorZero(#[from] DivisorZeroError), } -impl ClkConfig { +impl ClockConfig { + /// New clock config with the given divisor. pub fn new(div: u16) -> Self { Self { div } } + /// MSB part of the divisor. #[inline(always)] pub fn div_msb(&self) -> u8 { (self.div >> 8) as u8 } + /// LSB part of the divisor. #[inline(always)] pub fn div_lsb(&self) -> u8 { self.div as u8 @@ -71,7 +100,7 @@ impl ClkConfig { pub fn new_autocalc_with_error( clk_in: fugit::HertzU32, baudrate: u32, - ) -> Result<(Self, f32), ClkConfigError> { + ) -> Result<(Self, f32), ClockConfigError> { let cfg = Self::new_autocalc(clk_in, baudrate)?; Ok((cfg, cfg.calculate_error_rate(clk_in, baudrate)?)) } @@ -82,10 +111,10 @@ impl ClkConfig { /// to check the error rate, or use the [Self::new_autocalc_with_error] function to get both /// the clock config and its baud error. #[inline] - pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result { + pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result { let div = Self::calc_div_with_integer_div(clk_in, baudrate)?; if div > u16::MAX as u32 { - return Err(ClkConfigError::DivisorTooLargeError(div)); + return Err(ClockConfigError::DivisorTooLargeError(div)); } Ok(Self { div: div as u16 }) } @@ -101,6 +130,7 @@ impl ClkConfig { calculate_error_rate_from_div(clk_in, baudrate, self.div) } + /// Calculate the divisor from an input clock for a give target baudrate. #[inline(always)] pub const fn calc_div_with_integer_div( clk_in: fugit::HertzU32, @@ -114,30 +144,37 @@ impl ClkConfig { } } +/// Parity configuration. #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] pub enum Parity { + /// No parity (default). #[default] None, + /// Odd parity. Odd, + /// Even parity. Even, } +/// AXI UART16550 peripheral driver. pub struct AxiUart16550 { rx: Rx, tx: Tx, config: UartConfig, } +/// UART configuration structure. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct UartConfig { - clk: ClkConfig, + clk: ClockConfig, word_len: WordLen, parity: Parity, stop_bits: StopBits, } impl UartConfig { - pub const fn new_with_clk_config(clk: ClkConfig) -> Self { + /// New with the given clock configuration. + pub const fn new_with_clk_config(clk: ClockConfig) -> Self { Self { clk, word_len: WordLen::Eight, @@ -146,8 +183,9 @@ impl UartConfig { } } + /// New with all parameters. pub const fn new( - clk: ClkConfig, + clk: ClockConfig, word_len: WordLen, parity: Parity, stop_bits: StopBits, @@ -175,15 +213,15 @@ impl AxiUart16550 { /// with the same `base_addr` can lead to unintended behavior if not externally synchronized. /// - The driver performs **volatile** reads and writes to the provided address. pub unsafe fn new(base_addr: u32, config: UartConfig) -> Self { - let mut regs = unsafe { registers::AxiUart16550::new_mmio_at(base_addr as usize) }; + let mut regs = unsafe { registers::Registers::new_mmio_at(base_addr as usize) }; // This unlocks the divisor config registers. - regs.write_lcr(Lcr::new_for_divisor_access()); + regs.write_lcr(LineControl::new_for_divisor_access()); regs.write_fifo_or_dll(config.clk.div_lsb() as u32); regs.write_ier_or_dlm(config.clk.div_msb() as u32); // Configure all other settings and reset the div acess latch. This is important // for accessing IER and the FIFO control register again. regs.write_lcr( - Lcr::builder() + LineControl::builder() .with_div_access_latch(false) .with_set_break(false) .with_stick_parity(false) @@ -194,10 +232,10 @@ impl AxiUart16550 { .build(), ); // Disable all interrupts. - regs.write_ier_or_dlm(Ier::new_with_raw_value(0x0).raw_value()); + regs.write_ier_or_dlm(InterruptEnable::new_with_raw_value(0x0).raw_value()); // Enable FIFO, configure 8 bytes FIFO trigger by default. regs.write_iir_or_fcr( - Fcr::builder() + FifoControl::builder() .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) .with_dma_mode_sel(false) .with_reset_tx_fifo(true) @@ -213,11 +251,13 @@ impl AxiUart16550 { } } + /// Raw register access. #[inline(always)] - pub const fn regs(&mut self) -> &mut registers::MmioAxiUart16550<'static> { + pub const fn regs(&mut self) -> &mut registers::MmioRegisters<'static> { &mut self.rx.regs } + /// UART configuration. #[inline(always)] pub const fn config(&mut self) -> &UartConfig { &self.config @@ -231,19 +271,21 @@ impl AxiUart16550 { self.tx.write_fifo(data) } - // TODO: Make this non-mut as soon as pure reads are available. + /// Transmitter Holding Register empty status. #[inline(always)] - pub fn thr_empty(&mut self) -> bool { + pub fn thr_empty(&self) -> bool { self.tx.thr_empty() } + /// Transmitter empty status. #[inline(always)] - pub fn tx_empty(&mut self) -> bool { + pub fn tx_empty(&self) -> bool { self.tx.tx_empty() } + /// Receiver has data. #[inline(always)] - pub fn rx_has_data(&mut self) -> bool { + pub fn rx_has_data(&self) -> bool { self.rx.has_data() } @@ -255,21 +297,28 @@ impl AxiUart16550 { self.tx.write_fifo_unchecked(data); } + /// 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 { self.rx.read_fifo() } + /// 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() } + /// Enable interrupts according to the given interrupt enable configuration. #[inline(always)] - pub fn enable_interrupts(&mut self, ier: Ier) { + pub fn enable_interrupts(&mut self, ier: InterruptEnable) { self.regs().write_ier_or_dlm(ier.raw_value()); } + /// Split into TX and RX halves. pub fn split(self) -> (Tx, Rx) { (self.tx, self.rx) } @@ -320,18 +369,18 @@ impl embedded_io::Write for AxiUart16550 { #[cfg(test)] mod tests { - use crate::ClkConfigError; + use crate::ClockConfigError; //extern crate std; use super::{DivisorZeroError, calculate_error_rate_from_div}; - use super::ClkConfig; + use super::ClockConfig; use approx::abs_diff_eq; use fugit::RateExtU32; #[test] fn test_clk_calc_example_0() { - let clk_cfg = ClkConfig::new_autocalc(100.MHz(), 56000).unwrap(); + let clk_cfg = ClockConfig::new_autocalc(100.MHz(), 56000).unwrap(); // For some reason, the Xilinx example rounds up here.. assert_eq!(clk_cfg.div, 0x0070); assert_eq!(clk_cfg.div_msb(), 0x00); @@ -339,7 +388,7 @@ mod tests { let error = clk_cfg.calculate_error_rate(100.MHz(), 56000).unwrap(); assert!(abs_diff_eq!(error, 0.0035, epsilon = 0.001)); let (clk_cfg_checked, error_checked) = - ClkConfig::new_autocalc_with_error(100.MHz(), 56000).unwrap(); + ClockConfig::new_autocalc_with_error(100.MHz(), 56000).unwrap(); assert_eq!(clk_cfg, clk_cfg_checked); assert!(abs_diff_eq!(error, error_checked, epsilon = 0.001)); let error_calc = calculate_error_rate_from_div(100.MHz(), 56000, clk_cfg.div).unwrap(); @@ -348,7 +397,7 @@ mod tests { #[test] fn test_clk_calc_example_1() { - let clk_cfg = ClkConfig::new_autocalc(1843200.Hz(), 56000).unwrap(); + let clk_cfg = ClockConfig::new_autocalc(1843200.Hz(), 56000).unwrap(); assert_eq!(clk_cfg.div, 0x0002); assert_eq!(clk_cfg.div_msb(), 0x00); assert_eq!(clk_cfg.div_lsb(), 0x02); @@ -356,8 +405,11 @@ mod tests { #[test] fn test_invalid_baud() { - let clk_cfg = ClkConfig::new_autocalc_with_error(100.MHz(), 0); - assert_eq!(clk_cfg, Err(ClkConfigError::DivisorZero(DivisorZeroError))); + let clk_cfg = ClockConfig::new_autocalc_with_error(100.MHz(), 0); + assert_eq!( + clk_cfg, + Err(ClockConfigError::DivisorZero(DivisorZeroError)) + ); } #[test] diff --git a/src/registers.rs b/src/registers.rs index fc7759e..89584ee 100644 --- a/src/registers.rs +++ b/src/registers.rs @@ -1,14 +1,17 @@ +//! # Raw register module use arbitrary_int::u2; /// Transmitter Holding Register. #[bitbybit::bitfield(u32)] pub struct Fifo { + /// Bytes to transmit or receive. #[bits(0..=7, rw)] data: u8, } +/// Interrupt Enable Register. #[bitbybit::bitfield(u32)] -pub struct Ier { +pub struct InterruptEnable { /// Enable Modem Status Interrupt #[bit(3, rw)] modem_status: bool, @@ -26,36 +29,48 @@ pub struct Ier { /// Interrupt identification ID #[bitbybit::bitenum(u3, exhaustive = false)] #[derive(Debug, PartialEq, Eq)] -pub enum IntId2 { +pub enum InterruptId2 { + /// Receiver Line Status. ReceiverLineStatus = 0b011, + /// RX data available. RxDataAvailable = 0b010, + /// Character timeout. CharTimeout = 0b110, + /// THR empty. ThrEmpty = 0b001, + /// Modem status. ModemStatus = 0b000, } /// Interrupt Identification Register #[bitbybit::bitfield(u32)] -pub struct Iir { +pub struct InterruptIdentification { /// 16550 mode enabled? #[bits(6..=7, r)] fifo_enabled: u2, + /// Interrupt ID2. #[bits(1..=3, r)] - int_id: Option, + int_id: Option, /// Interrupt Pending, active low. #[bit(0, r)] int_pend_n: bool, } +/// RX FIFO trigger level. #[bitbybit::bitenum(u2, exhaustive = true)] pub enum RxFifoTrigger { + /// One byte. OneByte = 0b00, + /// 4 bytes. FourBytes = 0b01, + /// 8 bytes. EightBytes = 0b10, + /// 14 bytes. FourteenBytes = 0b11, } impl RxFifoTrigger { + /// Raw number instead of register value. pub const fn as_num(self) -> u32 { match self { RxFifoTrigger::OneByte => 1, @@ -68,59 +83,78 @@ impl RxFifoTrigger { /// FIFO Control Register #[bitbybit::bitfield(u32, default = 0x0)] -pub struct Fcr { +pub struct FifoControl { + /// RX FIFO trigger level. #[bits(4..=5, rw)] rx_fifo_trigger: RxFifoTrigger, + /// DMA mode select. #[bit(3, rw)] dma_mode_sel: bool, + /// Reset TX FIFO. #[bit(2, rw)] reset_tx_fifo: bool, + /// Reset RX FIFO. #[bit(1, rw)] reset_rx_fifo: bool, + /// FIFO enable. #[bit(0, rw)] fifo_enable: bool, } +/// Word length in bits. #[bitbybit::bitenum(u2, exhaustive = true)] #[derive(Default, Debug, PartialEq, Eq)] pub enum WordLen { + /// 5 bits. Five = 0b00, + /// 6 bits. Six = 0b01, + /// 7 bits. Seven = 0b10, + /// 8 bits (default). #[default] Eight = 0b11, } +/// Stop bits. #[bitbybit::bitenum(u1, exhaustive = true)] #[derive(Default, Debug, PartialEq, Eq)] pub enum StopBits { + /// One stop bit (default). #[default] One = 0b0, /// 1.5 for 5 bits/char, 2 otherwise. OnePointFiveOrTwo = 0b1, } -/// Line control register +/// Line control register. #[bitbybit::bitfield(u32, default = 0x00)] -pub struct Lcr { +pub struct LineControl { + /// Divisor Latch Access Bit. #[bit(7, rw)] div_access_latch: bool, + /// Set break bit. #[bit(6, rw)] set_break: bool, + /// Parity stick bit. #[bit(5, rw)] stick_parity: bool, + /// Even parity. #[bit(4, rw)] even_parity: bool, + /// Parity enable. #[bit(3, rw)] parity_enable: bool, /// 0: 1 stop bit, 1: 2 stop bits or 1.5 if 5 bits/char selected #[bit(2, rw)] stop_bits: StopBits, + /// Word length. #[bits(0..=1, rw)] word_len: WordLen, } -impl Lcr { +impl LineControl { + /// New line control value for accessing divisor latches. pub fn new_for_divisor_access() -> Self { Self::new_with_raw_value(0x80) } @@ -129,7 +163,8 @@ impl Lcr { /// Line Status Register #[bitbybit::bitfield(u32)] #[derive(Debug)] -pub struct Lsr { +pub struct LineStatus { + /// Error in RX FIFO. #[bit(7, rw)] error_in_rx_fifo: bool, /// In the FIFO mode, this is set to 1 when the TX FIFO and shift register are both empty. @@ -139,21 +174,27 @@ pub struct Lsr { /// in the TX shift register. #[bit(5, rw)] thr_empty: bool, + /// Break interrupt. #[bit(4, rw)] break_interrupt: bool, + /// Framing error. #[bit(3, rw)] framing_error: bool, + /// Parity error. #[bit(2, rw)] parity_error: bool, + /// Overrun error. #[bit(1, rw)] overrun_error: bool, + /// Data ready. #[bit(0, rw)] data_ready: bool, } +/// Raw register block. #[derive(derive_mmio::Mmio)] #[repr(C)] -pub struct AxiUart16550 { +pub struct Registers { _reserved: [u32; 0x400], /// FIFO register for LCR[7] == 0 or Divisor Latch (LSB) register for LCR[7] == 1 fifo_or_dll: u32, @@ -165,11 +206,11 @@ pub struct AxiUart16550 { /// write-only FIFO control register. iir_or_fcr: u32, /// Line Control Register - lcr: Lcr, + lcr: LineControl, /// Modem Control Register mcr: u32, /// Line Status Register - lsr: Lsr, + lsr: LineStatus, /// Modem Status Register msr: u32, /// Scratch Register diff --git a/src/rx.rs b/src/rx.rs index e5047aa..bc28765 100644 --- a/src/rx.rs +++ b/src/rx.rs @@ -1,10 +1,14 @@ +//! # Receiver (RX) support module use core::convert::Infallible; use crate::{ DEFAULT_RX_TRIGGER_LEVEL, - registers::{self, Fcr, Ier, Iir, IntId2, Lsr}, + registers::{ + self, FifoControl, InterruptEnable, InterruptId2, InterruptIdentification, LineStatus, + }, }; +/// RX errors structure. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct RxErrors { parity: bool, @@ -13,6 +17,8 @@ pub struct RxErrors { } impl RxErrors { + /// Construct a new empty [RxErrors] structure. + #[inline] pub const fn new() -> Self { Self { parity: false, @@ -21,26 +27,38 @@ impl RxErrors { } } + /// Parity error. + #[inline] pub const fn parity(&self) -> bool { self.parity } + /// Framing error. + #[inline] pub const fn frame(&self) -> bool { self.frame } + /// Overrun error. + #[inline] pub const fn overrun(&self) -> bool { self.overrun } + /// Error has occurred. + #[inline] pub const fn has_errors(&self) -> bool { self.parity || self.frame || self.overrun } } +/// AXI UARTLITE RX driver. +/// +/// Can be created by [super::AxiUart16550::split]ting a regular AXI UART16550 structure or +/// by [Self::steal]ing it unsafely. pub struct Rx { /// Internal MMIO register structure. - pub(crate) regs: registers::MmioAxiUart16550<'static>, + pub(crate) regs: registers::MmioRegisters<'static>, pub(crate) errors: Option, } @@ -60,15 +78,19 @@ impl Rx { /// The same safey rules specified in [super::AxiUart16550::new] apply. pub const unsafe fn steal(base_addr: usize) -> Self { Self { - regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, + regs: unsafe { registers::Registers::new_mmio_at(base_addr) }, errors: None, } } - pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { + pub(crate) fn new(regs: registers::MmioRegisters<'static>) -> Self { Self { regs, errors: None } } + /// 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_lsr(); @@ -76,11 +98,12 @@ impl Rx { return Err(nb::Error::WouldBlock); } if status_reg.error_in_rx_fifo() { - self.errors = Some(Self::lsr_to_errors(status_reg)); + self.errors = Some(Self::lsr_to_errors(&status_reg)); } Ok(self.read_fifo_unchecked()) } + /// Read from the FIFO without checking the FIFO fill status. #[inline(always)] pub fn read_fifo_unchecked(&mut self) -> u8 { self.regs.read_fifo_or_dll() as u8 @@ -99,30 +122,33 @@ impl Rx { self.enable_interrupt(); } + /// Enable RX interrupts. #[inline] pub fn enable_interrupt(&mut self) { self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); + let mut ier = InterruptEnable::new_with_raw_value(val); ier.set_rx_avl(true); ier.set_line_status(true); ier.raw_value() }); } + /// Disable RX interrupts. #[inline] pub fn disable_interrupt(&mut self) { self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); + let mut ier = InterruptEnable::new_with_raw_value(val); ier.set_rx_avl(false); ier.set_line_status(false); ier.raw_value() }); } + /// Reset the RX FIFO. #[inline] pub fn reset_fifo(&mut self) { self.regs.write_iir_or_fcr( - Fcr::builder() + FifoControl::builder() .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) .with_dma_mode_sel(false) .with_reset_tx_fifo(false) @@ -133,32 +159,38 @@ impl Rx { ); } + /// Data is available. #[inline(always)] - pub fn has_data(&mut self) -> bool { + pub fn has_data(&self) -> bool { self.regs.read_lsr().data_ready() } + /// Read the IIR register. #[inline] - pub fn read_iir(&mut self) -> Iir { - Iir::new_with_raw_value(self.regs.read_iir_or_fcr()) + pub fn read_iir(&mut self) -> InterruptIdentification { + InterruptIdentification::new_with_raw_value(self.regs.read_iir_or_fcr()) } + /// Should be called when a Line Status interrupt occurs. #[inline] - pub fn on_interrupt_receiver_line_status(&mut self, _iir: Iir) -> RxErrors { + pub fn on_interrupt_receiver_line_status(&mut self, _iir: InterruptIdentification) -> RxErrors { let lsr = self.regs.read_lsr(); - Self::lsr_to_errors(lsr) + Self::lsr_to_errors(&lsr) } + /// Should be called when a Data Available or Character Timeout interrupt occurs. + /// + /// Reads all available data into the provided buffer and returns the number of bytes read. #[inline] pub fn on_interrupt_data_available_or_char_timeout( &mut self, - int_id2: IntId2, + int_id2: InterruptId2, buf: &mut [u8; 16], ) -> usize { let mut read = 0; // It is guaranteed that we can read the FIFO trigger level. - if int_id2 == IntId2::RxDataAvailable { - let trigger_level = Fcr::new_with_raw_value(self.regs.read_iir_or_fcr()); + if int_id2 == InterruptId2::RxDataAvailable { + let trigger_level = FifoControl::new_with_raw_value(self.regs.read_iir_or_fcr()); (0..trigger_level.rx_fifo_trigger().as_num() as usize).for_each(|i| { buf[i] = self.read_fifo_unchecked(); read += 1; @@ -172,7 +204,8 @@ impl Rx { read } - pub fn lsr_to_errors(status_reg: Lsr) -> RxErrors { + /// Extract RX errors from the LSR register. + pub fn lsr_to_errors(status_reg: &LineStatus) -> RxErrors { let mut errors = RxErrors::new(); if status_reg.framing_error() { errors.frame = true; diff --git a/src/tx.rs b/src/tx.rs index d693fb8..caa6ab2 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -1,13 +1,18 @@ +//! # Transmitter (TX) support module use core::convert::Infallible; use crate::{ DEFAULT_RX_TRIGGER_LEVEL, - registers::{self, Fcr, Ier}, + registers::{self, FifoControl, InterruptEnable}, }; +/// AXI UART16550 TX driver. +/// +/// Can be created by [super::AxiUart16550::split]ting a regular AXI UARTLITE structure or +/// by [Self::steal]ing it unsafely. pub struct Tx { /// Internal MMIO register structure. - pub(crate) regs: registers::MmioAxiUart16550<'static>, + pub(crate) regs: registers::MmioRegisters<'static>, } impl Tx { @@ -26,14 +31,15 @@ impl Tx { /// The same safey rules specified in [super::AxiUart16550::new] apply. pub const unsafe fn steal(base_addr: usize) -> Self { Self { - regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, + regs: unsafe { registers::Registers::new_mmio_at(base_addr) }, } } - pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { + pub(crate) fn new(regs: registers::MmioRegisters<'static>) -> Self { Self { regs } } + /// Write a byte into the FIFO if there is space available. #[inline] pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { if !self.thr_empty() { @@ -43,19 +49,21 @@ impl Tx { Ok(()) } + /// Enable TX interrupts. #[inline] pub fn enable_interrupt(&mut self) { self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); + let mut ier = InterruptEnable::new_with_raw_value(val); ier.set_thr_empty(true); ier.raw_value() }); } + /// Disable TX interrupts. #[inline] pub fn disable_interrupt(&mut self) { self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); + let mut ier = InterruptEnable::new_with_raw_value(val); ier.set_thr_empty(false); ier.raw_value() }); @@ -69,21 +77,23 @@ impl Tx { self.regs.write_fifo_or_dll(data as u32); } - // TODO: Make this non-mut as soon as pure reads are available. + /// Transmitter Holding Register empty status. #[inline(always)] - pub fn thr_empty(&mut self) -> bool { + pub fn thr_empty(&self) -> bool { self.regs.read_lsr().thr_empty() } + /// Transmitter empty status. #[inline(always)] - pub fn tx_empty(&mut self) -> bool { + pub fn tx_empty(&self) -> bool { self.regs.read_lsr().tx_empty() } + /// Reset the FIFOs. #[inline] pub fn reset_fifo(&mut self) { self.regs.write_iir_or_fcr( - Fcr::builder() + FifoControl::builder() .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) .with_dma_mode_sel(false) .with_reset_tx_fifo(true) @@ -94,6 +104,7 @@ impl Tx { ); } + /// Should be called from the interrupt handler when a THR empty interrupt occurs. #[inline] pub fn on_interrupt_thr_empty(&mut self, next_write_chunk: &[u8]) -> usize { if next_write_chunk.is_empty() { diff --git a/src/tx_async.rs b/src/tx_async.rs index b6dd8e0..6be8eb6 100644 --- a/src/tx_async.rs +++ b/src/tx_async.rs @@ -24,19 +24,25 @@ use raw_slice::RawBufSlice; use crate::{ FIFO_DEPTH, Tx, - registers::{self, Ier}, + registers::{self, InterruptEnable}, }; +/// 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]; @@ -46,6 +52,7 @@ static TX_CONTEXTS: [Mutex>; NUM_WAKERS] = // critical section. static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS]; +/// Invalid waker index error. #[derive(Debug, thiserror::Error)] #[error("invalid waker slot index: {0}")] pub struct InvalidWakerIndex(pub usize); @@ -61,7 +68,7 @@ pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) { return; } let status = tx.regs.read_lsr(); - let ier = Ier::new_with_raw_value(tx.regs.read_ier_or_dlm()); + let ier = InterruptEnable::new_with_raw_value(tx.regs.read_ier_or_dlm()); // Interrupt are not even enabled. if !ier.thr_empty() { return; @@ -107,7 +114,7 @@ pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) { } #[derive(Debug, Copy, Clone)] -pub struct TxContext { +struct TxContext { progress: usize, slice: RawBufSlice, } @@ -122,9 +129,10 @@ impl TxContext { } } +/// TX future structure. pub struct TxFuture { waker_idx: usize, - reg_block: registers::MmioAxiUart16550<'static>, + reg_block: registers::MmioRegisters<'static>, } impl TxFuture { @@ -191,6 +199,7 @@ impl Drop for TxFuture { } } +/// Asynchronous TX driver. pub struct TxAsync { tx: Tx, waker_idx: usize, @@ -233,6 +242,7 @@ impl TxAsync { } } + /// Release the underlying TX handle. pub fn release(self) -> Tx { self.tx }