init commit
This commit is contained in:
commit
b51e44f478
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "axi-uartlite"
|
||||
version = "0.1.0"
|
||||
description = "LogiCORE AXI UART Lite v2.0 driver"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
derive-mmio = { git = "https://github.com/knurling-rs/derive-mmio.git", rev = "0806ce10b132ca15c6d9122a2d15a6e146b01520"}
|
||||
bitbybit = "1.3"
|
||||
arbitrary-int = "1.3"
|
||||
nb = "1"
|
||||
embedded-hal-nb = "1"
|
||||
embedded-io = "0.6"
|
||||
embedded-io-async = "0.6"
|
||||
critical-section = "1"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
embassy-sync = "0.6"
|
||||
raw-slice = { git = "https://egit.irs.uni-stuttgart.de/rust/raw-slice.git" }
|
||||
|
||||
[features]
|
||||
default = ["1-waker"]
|
||||
1-waker = []
|
||||
2-wakers = []
|
||||
4-wakers = []
|
||||
8-wakers = []
|
||||
16-wakers = []
|
||||
32-wakers = []
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
21
LICENSE-MIT
Normal file
21
LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Robin A. Mueller
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
264
src/lib.rs
Normal file
264
src/lib.rs
Normal file
@ -0,0 +1,264 @@
|
||||
//! # AXI UART Lite v2.0 driver
|
||||
//!
|
||||
//! This is a native Rust driver for the AMD AXI UART Lite v2.0 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]
|
||||
|
||||
use core::convert::Infallible;
|
||||
use registers::Control;
|
||||
pub mod registers;
|
||||
|
||||
pub mod tx;
|
||||
pub use tx::*;
|
||||
|
||||
pub mod rx;
|
||||
pub use rx::*;
|
||||
|
||||
pub mod tx_async;
|
||||
pub use tx_async::*;
|
||||
|
||||
pub const FIFO_DEPTH: usize = 16;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct RxErrorsCounted {
|
||||
parity: u8,
|
||||
frame: u8,
|
||||
overrun: u8,
|
||||
}
|
||||
|
||||
impl RxErrorsCounted {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
parity: 0,
|
||||
frame: 0,
|
||||
overrun: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn parity(&self) -> u8 {
|
||||
self.parity
|
||||
}
|
||||
|
||||
pub const fn frame(&self) -> u8 {
|
||||
self.frame
|
||||
}
|
||||
|
||||
pub const fn overrun(&self) -> u8 {
|
||||
self.overrun
|
||||
}
|
||||
|
||||
pub fn has_errors(&self) -> bool {
|
||||
self.parity > 0 || self.frame > 0 || self.overrun > 0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AxiUartlite {
|
||||
rx: Rx,
|
||||
tx: Tx,
|
||||
errors: RxErrorsCounted,
|
||||
}
|
||||
|
||||
impl AxiUartlite {
|
||||
/// Create a new AXI UART Lite peripheral driver.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The `base_addr` must be a valid memory-mapped register address of an AXI UART Lite peripheral.
|
||||
/// - Dereferencing an invalid or misaligned address results in **undefined behavior**.
|
||||
/// - The caller must ensure that no other code concurrently modifies the same peripheral registers
|
||||
/// in an unsynchronized manner to prevent data races.
|
||||
/// - This function does not enforce uniqueness of driver instances. Creating multiple instances
|
||||
/// 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 const unsafe fn new(base_addr: u32) -> Self {
|
||||
let regs = unsafe { registers::AxiUartlite::new_mmio_at(base_addr as usize) };
|
||||
Self {
|
||||
rx: Rx {
|
||||
regs: unsafe { regs.clone() },
|
||||
errors: None,
|
||||
},
|
||||
tx: Tx { regs, errors: None },
|
||||
errors: RxErrorsCounted::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn regs(&mut self) -> &mut registers::MmioAxiUartlite<'static> {
|
||||
&mut self.tx.regs
|
||||
}
|
||||
|
||||
/// Write into the UART Lite.
|
||||
///
|
||||
/// Returns [nb::Error::WouldBlock] if the TX FIFO is full.
|
||||
#[inline]
|
||||
pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> {
|
||||
self.tx.write_fifo(data).unwrap();
|
||||
if let Some(errors) = self.tx.errors {
|
||||
self.handle_status_reg_errors(errors);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write into the FIFO without checking the FIFO fill status.
|
||||
///
|
||||
/// This can be useful to completely fill the FIFO if it is known to be empty.
|
||||
#[inline(always)]
|
||||
pub fn write_fifo_unchecked(&mut self, data: u8) {
|
||||
self.tx.write_fifo_unchecked(data);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
|
||||
let val = self.rx.read_fifo().unwrap();
|
||||
if let Some(errors) = self.rx.errors {
|
||||
self.handle_status_reg_errors(errors);
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
#[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
|
||||
#[inline(always)]
|
||||
pub fn tx_fifo_empty(&mut self) -> bool {
|
||||
self.tx.fifo_empty()
|
||||
}
|
||||
|
||||
// TODO: Make this non-mut as soon as pure reads are available
|
||||
#[inline(always)]
|
||||
pub fn tx_fifo_full(&mut self) -> bool {
|
||||
self.tx.fifo_full()
|
||||
}
|
||||
|
||||
// TODO: Make this non-mut as soon as pure reads are available
|
||||
#[inline(always)]
|
||||
pub fn rx_has_data(&mut self) -> bool {
|
||||
self.rx.has_data()
|
||||
}
|
||||
|
||||
/// Read the error counters and also resets them.
|
||||
pub fn read_and_clear_errors(&mut self) -> RxErrorsCounted {
|
||||
let errors = self.errors;
|
||||
self.errors = RxErrorsCounted::new();
|
||||
errors
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn handle_status_reg_errors(&mut self, errors: RxErrors) {
|
||||
if errors.frame() {
|
||||
self.errors.frame = self.errors.frame.saturating_add(1);
|
||||
}
|
||||
if errors.parity() {
|
||||
self.errors.parity = self.errors.parity.saturating_add(1);
|
||||
}
|
||||
if errors.overrun() {
|
||||
self.errors.overrun = self.errors.overrun.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_rx_fifo(&mut self) {
|
||||
self.regs().write_ctrl_reg(
|
||||
Control::builder()
|
||||
.with_enable_interrupt(false)
|
||||
.with_reset_rx_fifo(true)
|
||||
.with_reset_tx_fifo(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_tx_fifo(&mut self) {
|
||||
self.regs().write_ctrl_reg(
|
||||
Control::builder()
|
||||
.with_enable_interrupt(false)
|
||||
.with_reset_rx_fifo(false)
|
||||
.with_reset_tx_fifo(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split(self) -> (Tx, Rx) {
|
||||
(self.tx, self.rx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn enable_interrupt(&mut self) {
|
||||
self.regs().write_ctrl_reg(
|
||||
Control::builder()
|
||||
.with_enable_interrupt(true)
|
||||
.with_reset_rx_fifo(false)
|
||||
.with_reset_tx_fifo(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn disable_interrupt(&mut self) {
|
||||
self.regs().write_ctrl_reg(
|
||||
Control::builder()
|
||||
.with_enable_interrupt(false)
|
||||
.with_reset_rx_fifo(false)
|
||||
.with_reset_tx_fifo(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::ErrorType for AxiUartlite {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::Write for AxiUartlite {
|
||||
#[inline]
|
||||
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
|
||||
self.tx.write(word)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> nb::Result<(), Self::Error> {
|
||||
self.tx.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::Read for AxiUartlite {
|
||||
#[inline]
|
||||
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
||||
self.rx.read()
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::ErrorType for AxiUartlite {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_io::Read for AxiUartlite {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.rx.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::Write for AxiUartlite {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.tx.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.tx.flush()
|
||||
}
|
||||
}
|
55
src/registers.rs
Normal file
55
src/registers.rs
Normal file
@ -0,0 +1,55 @@
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct RxFifo {
|
||||
#[bits(0..=7, r)]
|
||||
pub data: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct TxFifo {
|
||||
#[bits(0..=7, w)]
|
||||
pub data: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Status {
|
||||
#[bit(7, r)]
|
||||
pub parity_error: bool,
|
||||
#[bit(6, r)]
|
||||
pub frame_error: bool,
|
||||
#[bit(5, r)]
|
||||
pub overrun_error: bool,
|
||||
#[bit(4, r)]
|
||||
pub intr_enabled: bool,
|
||||
#[bit(3, r)]
|
||||
pub tx_fifo_full: bool,
|
||||
#[bit(2, r)]
|
||||
pub tx_fifo_empty: bool,
|
||||
#[bit(1, r)]
|
||||
pub rx_fifo_full: bool,
|
||||
/// RX FIFO contains valid data.
|
||||
#[bit(0, r)]
|
||||
pub rx_fifo_valid_data: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Control {
|
||||
#[bit(4, w)]
|
||||
enable_interrupt: bool,
|
||||
#[bit(1, w)]
|
||||
reset_rx_fifo: bool,
|
||||
#[bit(0, w)]
|
||||
reset_tx_fifo: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct AxiUartlite {
|
||||
#[mmio(RO)]
|
||||
rx_fifo: RxFifo,
|
||||
tx_fifo: TxFifo,
|
||||
#[mmio(RO)]
|
||||
stat_reg: Status,
|
||||
ctrl_reg: Control,
|
||||
}
|
||||
|
||||
unsafe impl Send for MmioAxiUartlite<'static> {}
|
172
src/rx.rs
Normal file
172
src/rx.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use core::convert::Infallible;
|
||||
|
||||
use crate::registers::{self, AxiUartlite, Status};
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct RxErrors {
|
||||
parity: bool,
|
||||
frame: bool,
|
||||
overrun: bool,
|
||||
}
|
||||
|
||||
impl RxErrors {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
parity: false,
|
||||
frame: false,
|
||||
overrun: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn parity(&self) -> bool {
|
||||
self.parity
|
||||
}
|
||||
|
||||
pub const fn frame(&self) -> bool {
|
||||
self.frame
|
||||
}
|
||||
|
||||
pub const fn overrun(&self) -> bool {
|
||||
self.overrun
|
||||
}
|
||||
|
||||
pub const fn has_errors(&self) -> bool {
|
||||
self.parity || self.frame || self.overrun
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rx {
|
||||
pub(crate) regs: registers::MmioAxiUartlite<'static>,
|
||||
pub(crate) errors: Option<RxErrors>,
|
||||
}
|
||||
|
||||
impl Rx {
|
||||
/// Steal the RX part of the UART Lite.
|
||||
///
|
||||
/// You should only use this if you can not use the regular [super::AxiUartlite] constructor
|
||||
/// and the [super::AxiUartlite::split] method.
|
||||
///
|
||||
/// This function assumes that the setup of the UART was already done.
|
||||
/// It can be used to create an RX handle inside an interrupt handler without having to use
|
||||
/// a [critical_section::Mutex] if the user can guarantee that the RX handle will only be
|
||||
/// used by the interrupt handler or only interrupt specific API will be used.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The same safey rules specified in [super::AxiUartlite] apply.
|
||||
#[inline]
|
||||
pub const unsafe fn steal(base_addr: usize) -> Self {
|
||||
Self {
|
||||
regs: unsafe { AxiUartlite::new_mmio_at(base_addr) },
|
||||
errors: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
|
||||
let status_reg = self.regs.read_stat_reg();
|
||||
if !status_reg.rx_fifo_valid_data() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
let val = self.read_fifo_unchecked();
|
||||
if let Some(errors) = handle_status_reg_errors(&status_reg) {
|
||||
self.errors = Some(errors);
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
#[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
|
||||
#[inline(always)]
|
||||
pub fn has_data(&mut self) -> bool {
|
||||
self.regs.read_stat_reg().rx_fifo_valid_data()
|
||||
}
|
||||
|
||||
/// This simply reads all available bytes in the RX FIFO.
|
||||
///
|
||||
/// It returns the number of read bytes.
|
||||
#[inline]
|
||||
pub fn read_whole_fifo(&mut self, buf: &mut [u8; 16]) -> usize {
|
||||
let mut read = 0;
|
||||
while read < buf.len() {
|
||||
match self.read_fifo() {
|
||||
Ok(byte) => {
|
||||
buf[read] = byte;
|
||||
read += 1;
|
||||
}
|
||||
Err(nb::Error::WouldBlock) => break,
|
||||
}
|
||||
}
|
||||
read
|
||||
}
|
||||
|
||||
/// Can be called in the interrupt handler for the UART Lite to handle RX reception.
|
||||
///
|
||||
/// Simply calls [Rx::read_whole_fifo].
|
||||
#[inline]
|
||||
pub fn on_interrupt_rx(&mut self, buf: &mut [u8; 16]) -> usize {
|
||||
self.read_whole_fifo(buf)
|
||||
}
|
||||
|
||||
pub fn read_and_clear_last_error(&mut self) -> Option<RxErrors> {
|
||||
let errors = self.errors?;
|
||||
self.errors = None;
|
||||
Some(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::ErrorType for Rx {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::Read for Rx {
|
||||
#[inline]
|
||||
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
||||
self.read_fifo()
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::ErrorType for Rx {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_io::Read for Rx {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
while !self.has_data() {}
|
||||
let mut read = 0;
|
||||
for byte in buf.iter_mut() {
|
||||
match self.read_fifo() {
|
||||
Ok(data) => {
|
||||
*byte = data;
|
||||
read += 1;
|
||||
}
|
||||
Err(nb::Error::WouldBlock) => break,
|
||||
}
|
||||
}
|
||||
Ok(read)
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn handle_status_reg_errors(status_reg: &Status) -> Option<RxErrors> {
|
||||
let mut errors = RxErrors::new();
|
||||
if status_reg.frame_error() {
|
||||
errors.frame = true;
|
||||
}
|
||||
if status_reg.parity_error() {
|
||||
errors.parity = true;
|
||||
}
|
||||
if status_reg.overrun_error() {
|
||||
errors.overrun = true;
|
||||
}
|
||||
if !errors.has_errors() {
|
||||
return None;
|
||||
}
|
||||
Some(errors)
|
||||
}
|
142
src/tx.rs
Normal file
142
src/tx.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use core::convert::Infallible;
|
||||
|
||||
use crate::{
|
||||
RxErrors, handle_status_reg_errors,
|
||||
registers::{self, Control, TxFifo},
|
||||
};
|
||||
|
||||
pub struct Tx {
|
||||
pub(crate) regs: registers::MmioAxiUartlite<'static>,
|
||||
pub(crate) errors: Option<RxErrors>,
|
||||
}
|
||||
|
||||
impl Tx {
|
||||
/// Steal the TX part of the UART Lite.
|
||||
///
|
||||
/// You should only use this if you can not use the regular [super::AxiUartlite] constructor
|
||||
/// and the [super::AxiUartlite::split] method.
|
||||
///
|
||||
/// This function assumes that the setup of the UART was already done.
|
||||
/// It can be used to create a TX handle inside an interrupt handler without having to use
|
||||
/// a [critical_section::Mutex] if the user can guarantee that the TX handle will only be
|
||||
/// used by the interrupt handler, or only interrupt specific API will be used.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The same safey rules specified in [super::AxiUartlite] apply.
|
||||
pub unsafe fn steal(base_addr: usize) -> Self {
|
||||
let regs = unsafe { registers::AxiUartlite::new_mmio_at(base_addr) };
|
||||
Self { regs, errors: None }
|
||||
}
|
||||
|
||||
/// Write into the UART Lite.
|
||||
///
|
||||
/// Returns [nb::Error::WouldBlock] if the TX FIFO is full.
|
||||
#[inline]
|
||||
pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> {
|
||||
let status_reg = self.regs.read_stat_reg();
|
||||
if status_reg.tx_fifo_full() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
self.write_fifo_unchecked(data);
|
||||
if let Some(errors) = handle_status_reg_errors(&status_reg) {
|
||||
self.errors = Some(errors);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_fifo(&mut self) {
|
||||
let status = self.regs.read_stat_reg();
|
||||
self.regs.write_ctrl_reg(
|
||||
Control::builder()
|
||||
.with_enable_interrupt(status.intr_enabled())
|
||||
.with_reset_rx_fifo(false)
|
||||
.with_reset_tx_fifo(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Write into the FIFO without checking the FIFO fill status.
|
||||
///
|
||||
/// This can be useful to completely fill the FIFO if it is known to be empty.
|
||||
#[inline(always)]
|
||||
pub fn write_fifo_unchecked(&mut self, data: u8) {
|
||||
self.regs
|
||||
.write_tx_fifo(TxFifo::new_with_raw_value(data as u32));
|
||||
}
|
||||
|
||||
// TODO: Make this non-mut as soon as pure reads are available
|
||||
#[inline(always)]
|
||||
pub fn fifo_empty(&mut self) -> bool {
|
||||
self.regs.read_stat_reg().tx_fifo_empty()
|
||||
}
|
||||
|
||||
// TODO: Make this non-mut as soon as pure reads are available
|
||||
#[inline(always)]
|
||||
pub fn fifo_full(&mut self) -> bool {
|
||||
self.regs.read_stat_reg().tx_fifo_full()
|
||||
}
|
||||
|
||||
/// Fills the FIFO with user provided data until the user data
|
||||
/// is consumed or the FIFO is full.
|
||||
///
|
||||
/// Returns the amount of written data, which might be smaller than the buffer size.
|
||||
pub fn fill_fifo(&mut self, buf: &[u8]) -> usize {
|
||||
let mut written = 0;
|
||||
while written < buf.len() {
|
||||
match self.write_fifo(buf[written]) {
|
||||
Ok(_) => written += 1,
|
||||
Err(nb::Error::WouldBlock) => break,
|
||||
}
|
||||
}
|
||||
written
|
||||
}
|
||||
|
||||
pub fn read_and_clear_last_error(&mut self) -> Option<RxErrors> {
|
||||
let errors = self.errors?;
|
||||
self.errors = None;
|
||||
Some(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::ErrorType for Tx {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal_nb::serial::Write for Tx {
|
||||
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
|
||||
self.write_fifo(word)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> nb::Result<(), Self::Error> {
|
||||
while !self.fifo_empty() {}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::ErrorType for Tx {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_io::Write for Tx {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
while self.fifo_full() {}
|
||||
let mut written = 0;
|
||||
for &byte in buf.iter() {
|
||||
match self.write_fifo(byte) {
|
||||
Ok(_) => written += 1,
|
||||
Err(nb::Error::WouldBlock) => break,
|
||||
}
|
||||
}
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
while !self.fifo_empty() {}
|
||||
Ok(())
|
||||
}
|
||||
}
|
221
src/tx_async.rs
Normal file
221
src/tx_async.rs
Normal file
@ -0,0 +1,221 @@
|
||||
//! # Asynchronous TX support.
|
||||
//!
|
||||
//! This module provides support for asynchronous non-blocking TX transfers.
|
||||
//!
|
||||
//! It provides a static number of async wakers to allow a configurable amount of pollable
|
||||
//! [TxFuture]s. Each UARTLite [Tx] instance which performs asynchronous TX operations needs
|
||||
//! to be to explicitely assigned a waker when creating an awaitable [TxAsync] structure
|
||||
//! as well as when calling the [on_interrupt_tx] handler.
|
||||
//!
|
||||
//! The maximum number of available wakers is configured via the waker feature flags:
|
||||
//!
|
||||
//! - `1-waker`
|
||||
//! - `2-wakers`
|
||||
//! - `4-wakers`
|
||||
//! - `8-wakers`
|
||||
//! - `16-wakers`
|
||||
//! - `32-wakers`
|
||||
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use raw_slice::RawBufSlice;
|
||||
|
||||
use crate::{FIFO_DEPTH, Tx};
|
||||
|
||||
#[cfg(feature = "1-waker")]
|
||||
pub const NUM_WAKERS: usize = 1;
|
||||
#[cfg(feature = "2-wakers")]
|
||||
pub const NUM_WAKERS: usize = 2;
|
||||
#[cfg(feature = "4-wakers")]
|
||||
pub const NUM_WAKERS: usize = 4;
|
||||
#[cfg(feature = "8-wakers")]
|
||||
pub const NUM_WAKERS: usize = 8;
|
||||
#[cfg(feature = "16-wakers")]
|
||||
pub const NUM_WAKERS: usize = 16;
|
||||
#[cfg(feature = "32-wakers")]
|
||||
pub const NUM_WAKERS: usize = 32;
|
||||
static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
|
||||
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; NUM_WAKERS] =
|
||||
[const { Mutex::new(RefCell::new(TxContext::new())) }; NUM_WAKERS];
|
||||
// Completion flag. Kept outside of the context structure as an atomic to avoid
|
||||
// critical section.
|
||||
static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS];
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("invalid waker slot index: {0}")]
|
||||
pub struct InvalidWakerIndex(pub usize);
|
||||
|
||||
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
|
||||
/// 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.
|
||||
pub fn on_interrupt_tx(uartlite_tx: &mut Tx, waker_slot: usize) {
|
||||
if waker_slot >= NUM_WAKERS {
|
||||
return;
|
||||
}
|
||||
let status = uartlite_tx.regs.read_stat_reg();
|
||||
// Interrupt are not even enabled.
|
||||
if !status.intr_enabled() {
|
||||
return;
|
||||
}
|
||||
let mut context = critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[waker_slot].borrow(cs);
|
||||
*context_ref.borrow()
|
||||
});
|
||||
// No transfer active.
|
||||
if context.slice.is_null() {
|
||||
return;
|
||||
}
|
||||
let slice_len = context.slice.len().unwrap();
|
||||
if (context.progress >= slice_len && status.tx_fifo_empty()) || slice_len == 0 {
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[waker_slot].borrow(cs);
|
||||
*context_ref.borrow_mut() = context;
|
||||
});
|
||||
// Transfer is done.
|
||||
TX_DONE[waker_slot].store(true, core::sync::atomic::Ordering::Relaxed);
|
||||
UART_TX_WAKERS[waker_slot].wake();
|
||||
return;
|
||||
}
|
||||
// Safety: We documented that the user provided slice must outlive the future, so we convert
|
||||
// the raw pointer back to the slice here.
|
||||
let slice = unsafe { context.slice.get() }.expect("slice is invalid");
|
||||
while context.progress < slice_len {
|
||||
if uartlite_tx.regs.read_stat_reg().tx_fifo_full() {
|
||||
break;
|
||||
}
|
||||
// Safety: TX structure is owned by the future which does not write into the the data
|
||||
// register, so we can assume we are the only one writing to the data register.
|
||||
uartlite_tx.write_fifo_unchecked(slice[context.progress]);
|
||||
context.progress += 1;
|
||||
}
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[waker_slot].borrow(cs);
|
||||
*context_ref.borrow_mut() = context;
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TxContext {
|
||||
progress: usize,
|
||||
slice: RawBufSlice,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl TxContext {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
progress: 0,
|
||||
slice: RawBufSlice::new_nulled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxFuture {
|
||||
waker_idx: usize,
|
||||
}
|
||||
|
||||
impl TxFuture {
|
||||
/// Create a new TX future which can be used for asynchronous TX operations.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// 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,
|
||||
waker_idx: usize,
|
||||
data: &[u8],
|
||||
) -> Result<Self, InvalidWakerIndex> {
|
||||
TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||
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);
|
||||
}
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[waker_idx].borrow(cs);
|
||||
let mut context = context_ref.borrow_mut();
|
||||
unsafe {
|
||||
context.slice.set(data);
|
||||
}
|
||||
context.progress = init_fill_count;
|
||||
});
|
||||
Ok(Self { waker_idx })
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for TxFuture {
|
||||
type Output = usize;
|
||||
|
||||
fn poll(
|
||||
self: core::pin::Pin<&mut Self>,
|
||||
cx: &mut core::task::Context<'_>,
|
||||
) -> core::task::Poll<Self::Output> {
|
||||
UART_TX_WAKERS[self.waker_idx].register(cx.waker());
|
||||
if TX_DONE[self.waker_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||
let progress = critical_section::with(|cs| {
|
||||
let mut ctx = TX_CONTEXTS[self.waker_idx].borrow(cs).borrow_mut();
|
||||
ctx.slice.set_null();
|
||||
ctx.progress
|
||||
});
|
||||
return core::task::Poll::Ready(progress);
|
||||
}
|
||||
core::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TxFuture {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
pub struct TxAsync {
|
||||
tx: Tx,
|
||||
waker_idx: usize,
|
||||
}
|
||||
|
||||
impl TxAsync {
|
||||
pub fn new(tx: Tx, waker_idx: usize) -> Result<Self, InvalidWakerIndex> {
|
||||
if waker_idx >= NUM_WAKERS {
|
||||
return Err(InvalidWakerIndex(waker_idx));
|
||||
}
|
||||
Ok(Self { tx, waker_idx })
|
||||
}
|
||||
|
||||
/// Write a buffer asynchronously.
|
||||
///
|
||||
/// This implementation is not side effect free, and a started future might have already
|
||||
/// written part of the passed buffer.
|
||||
pub async fn write(&mut self, buf: &[u8]) -> usize {
|
||||
if buf.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let fut = unsafe { TxFuture::new(&mut self.tx, self.waker_idx, buf).unwrap() };
|
||||
fut.await
|
||||
}
|
||||
|
||||
pub fn release(self) -> Tx {
|
||||
self.tx
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::ErrorType for TxAsync {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_io_async::Write for TxAsync {
|
||||
/// Write a buffer asynchronously.
|
||||
///
|
||||
/// This implementation is not side effect free, and a started future might have already
|
||||
/// written part of the passed buffer.
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
Ok(self.write(buf).await)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user