Merge pull request 'prep v0.1.0' (#1) from prep-v0.1.0 into main
Some checks failed
ci / Check build (macos-latest) (push) Has been cancelled
ci / Check build (ubuntu-latest) (push) Has been cancelled
ci / Check build (windows-latest) (push) Has been cancelled
ci / Check MSRV (push) Has been cancelled
ci / Check Cross-Compilation (armv7-unknown-linux-gnueabihf) (push) Has been cancelled
ci / Check Cross-Compilation (armv7a-none-eabi) (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2025-11-28 11:49:47 +01:00
10 changed files with 368 additions and 72 deletions

65
.github/workflows/ci.yml vendored Normal file
View File

@@ -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

16
CHANGELOG.md Normal file
View File

@@ -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

View File

@@ -1,12 +1,17 @@
[package]
name = "axi-uart16550"
version = "0.1.0"
description = "AXI UART16550 IP core driver"
author = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
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"

28
README.md Normal file
View File

@@ -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`

35
justfile Normal file
View File

@@ -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

View File

@@ -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<Self, ClkConfigError> {
pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result<Self, ClockConfigError> {
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<u8, Infallible> {
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]

View File

@@ -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<IntId2>,
int_id: Option<InterruptId2>,
/// 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

View File

@@ -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<RxErrors>,
}
@@ -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<u8, Infallible> {
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;

View File

@@ -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() {

View File

@@ -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<RefCell<TxContext>>; 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<D: DelayNs> {
tx: Tx,
waker_idx: usize,
@@ -233,6 +242,7 @@ impl<D: DelayNs> TxAsync<D> {
}
}
/// Release the underlying TX handle.
pub fn release(self) -> Tx {
self.tx
}