commit 5ae4e40d677cc1b375c1c9a2065f1c2a50044531 Author: Robin Mueller Date: Mon Mar 31 19:44:30 2025 +0200 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7ad2b37 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "axi-uart16550" +version = "0.1.0" +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" +libm = "0.2" +critical-section = "1" +thiserror = { version = "2", default-features = false } +fugit = "0.3" +embedded-hal-async = "1" +embedded-hal-nb = "1" +embedded-io = "0.6" +embedded-io-async = "0.6" +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 = [] + +[dev-dependencies] +approx = "0.5" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -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. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..8311204 --- /dev/null +++ b/LICENSE-MIT @@ -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. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f9329bc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,372 @@ +#![no_std] + +use core::convert::Infallible; + +use registers::{Fcr, Ier, Lcr, RxFifoTrigger, StopBits, WordLen}; +pub mod registers; + +pub mod tx; +pub use tx::*; + +pub mod tx_async; +pub use tx_async::*; + +pub mod rx; +pub use rx::*; + +pub const FIFO_DEPTH: usize = 16; +pub const DEFAULT_RX_TRIGGER_LEVEL: RxFifoTrigger = RxFifoTrigger::EightBytes; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct ClkConfig { + pub div: u16, +} + +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[error("divisor is zero")] +pub struct DivisorZeroError; + +/// Calculate the error rate of the baudrate with the given clock frequency, baudrate and +/// divisor as a floating point value between 0.0 and 1.0. +#[inline] +pub fn calculate_error_rate_from_div( + clk_in: fugit::HertzU32, + baudrate: u32, + div: u16, +) -> Result { + if baudrate == 0 || div == 0 { + return Err(DivisorZeroError); + } + let actual = (clk_in.raw() as f32) / (16.0 * div as f32); + Ok(libm::fabsf(actual - baudrate as f32) / baudrate as f32) +} + +/// If this error occurs, the calculated baudrate divisor is too large, either because the +/// 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 { + DivisorTooLargeError(u32), + DivisorZero(#[from] DivisorZeroError), +} + +impl ClkConfig { + pub fn new(div: u16) -> Self { + Self { div } + } + + #[inline(always)] + pub fn div_msb(&self) -> u8 { + (self.div >> 8) as u8 + } + + #[inline(always)] + pub fn div_lsb(&self) -> u8 { + self.div as u8 + } + + /// This function calculates the required divisor values for a given input clock and baudrate + /// as well as an baud error rate. + #[inline] + pub fn new_autocalc_with_error( + clk_in: fugit::HertzU32, + baudrate: u32, + ) -> Result<(Self, f32), ClkConfigError> { + let cfg = Self::new_autocalc(clk_in, baudrate)?; + Ok((cfg, cfg.calculate_error_rate(clk_in, baudrate)?)) + } + + /// This function calculates the required divisor values for a given input clock and baudrate. + /// + /// The function will not calculate the error rate. You can use [Self::calculate_error_rate] + /// 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 { + let div = Self::calc_div_with_integer_div(clk_in, baudrate)?; + if div > u16::MAX as u32 { + return Err(ClkConfigError::DivisorTooLargeError(div)); + } + Ok(Self { div: div as u16 }) + } + + /// Calculate the error rate of the baudrate with the given clock frequency, baudrate and the + /// current clock config as a floating point value between 0.0 and 1.0. + #[inline] + pub fn calculate_error_rate( + &self, + clk_in: fugit::HertzU32, + baudrate: u32, + ) -> Result { + calculate_error_rate_from_div(clk_in, baudrate, self.div) + } + + #[inline(always)] + pub const fn calc_div_with_integer_div( + clk_in: fugit::HertzU32, + baudrate: u32, + ) -> Result { + if baudrate == 0 { + return Err(DivisorZeroError); + } + // Rounding integer division, by adding half the divisor to the dividend. + Ok((clk_in.raw() + (8 * baudrate)) / (16 * baudrate)) + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +pub enum Parity { + #[default] + None, + Odd, + Even, +} + +pub struct AxiUart16550 { + rx: Rx, + tx: Tx, + config: UartConfig, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct UartConfig { + clk: ClkConfig, + word_len: WordLen, + parity: Parity, + stop_bits: StopBits, +} + +impl UartConfig { + pub const fn new_with_clk_config(clk: ClkConfig) -> Self { + Self { + clk, + word_len: WordLen::Eight, + parity: Parity::None, + stop_bits: StopBits::One, + } + } + + pub const fn new( + clk: ClkConfig, + word_len: WordLen, + parity: Parity, + stop_bits: StopBits, + ) -> Self { + Self { + clk, + word_len, + parity, + stop_bits, + } + } +} + +impl AxiUart16550 { + /// Create a new AXI UART16550 peripheral driver. + /// + /// # Safety + /// + /// - The `base_addr` must be a valid memory-mapped register address of an AXI UART 16550 + /// 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 unsafe fn new(base_addr: u32, config: UartConfig) -> Self { + let mut regs = unsafe { registers::AxiUart16550::new_mmio_at(base_addr as usize) }; + // This unlocks the divisor config registers. + regs.write_lcr(Lcr::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() + .with_div_access_latch(false) + .with_set_break(false) + .with_stick_parity(false) + .with_even_parity(config.parity == Parity::Even) + .with_parity_enable(config.parity != Parity::None) + .with_stop_bits(config.stop_bits) + .with_word_len(config.word_len) + .build(), + ); + // Disable all interrupts. + regs.write_ier_or_dlm(Ier::new_with_raw_value(0x0).raw_value()); + // Enable FIFO, configure 8 bytes FIFO trigger by default. + regs.write_iir_or_fcr( + Fcr::builder() + .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) + .with_dma_mode_sel(false) + .with_reset_tx_fifo(true) + .with_reset_rx_fifo(true) + .with_fifo_enable(true) + .build() + .raw_value(), + ); + Self { + rx: Rx::new(unsafe { regs.clone() }), + tx: Tx::new(regs), + config, + } + } + + #[inline(always)] + pub const fn regs(&mut self) -> &mut registers::MmioAxiUart16550<'static> { + &mut self.rx.regs + } + + #[inline(always)] + pub const fn config(&mut self) -> &UartConfig { + &self.config + } + + /// 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) + } + + // TODO: Make this non-mut as soon as pure reads are available. + #[inline(always)] + pub fn thr_empty(&mut self) -> bool { + self.tx.thr_empty() + } + + #[inline(always)] + pub fn tx_empty(&mut self) -> bool { + self.tx.tx_empty() + } + + #[inline(always)] + pub fn rx_has_data(&mut self) -> bool { + self.rx.has_data() + } + + /// 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 { + self.rx.read_fifo() + } + + #[inline(always)] + pub fn read_fifo_unchecked(&mut self) -> u8 { + self.rx.read_fifo_unchecked() + } + + #[inline(always)] + pub fn enable_interrupts(&mut self, ier: Ier) { + self.regs().write_ier_or_dlm(ier.raw_value()); + } + + pub fn split(self) -> (Tx, Rx) { + (self.tx, self.rx) + } +} + +impl embedded_hal_nb::serial::ErrorType for AxiUart16550 { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Write for AxiUart16550 { + #[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 AxiUart16550 { + #[inline] + fn read(&mut self) -> nb::Result { + self.rx.read() + } +} + +impl embedded_io::ErrorType for AxiUart16550 { + type Error = Infallible; +} + +impl embedded_io::Read for AxiUart16550 { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf) + } +} + +impl embedded_io::Write for AxiUart16550 { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush() + } +} + +#[cfg(test)] +mod tests { + use crate::ClkConfigError; + + //extern crate std; + use super::{DivisorZeroError, calculate_error_rate_from_div}; + + use super::ClkConfig; + 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(); + // For some reason, the Xilinx example rounds up here.. + assert_eq!(clk_cfg.div, 0x0070); + assert_eq!(clk_cfg.div_msb(), 0x00); + assert_eq!(clk_cfg.div_lsb(), 0x70); + 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(); + 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(); + assert!(abs_diff_eq!(error, error_calc, epsilon = 0.001)); + } + + #[test] + fn test_clk_calc_example_1() { + let clk_cfg = ClkConfig::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); + } + + #[test] + fn test_invalid_baud() { + let clk_cfg = ClkConfig::new_autocalc_with_error(100.MHz(), 0); + assert_eq!(clk_cfg, Err(ClkConfigError::DivisorZero(DivisorZeroError))); + } + + #[test] + fn test_invalid_div() { + let error = calculate_error_rate_from_div(100.MHz(), 115200, 0); + assert_eq!(error.unwrap_err(), DivisorZeroError); + let error = calculate_error_rate_from_div(100.MHz(), 0, 0); + assert_eq!(error.unwrap_err(), DivisorZeroError); + let error = calculate_error_rate_from_div(100.MHz(), 0, 16); + assert_eq!(error.unwrap_err(), DivisorZeroError); + } +} diff --git a/src/registers.rs b/src/registers.rs new file mode 100644 index 0000000..fc7759e --- /dev/null +++ b/src/registers.rs @@ -0,0 +1,177 @@ +use arbitrary_int::u2; + +/// Transmitter Holding Register. +#[bitbybit::bitfield(u32)] +pub struct Fifo { + #[bits(0..=7, rw)] + data: u8, +} + +#[bitbybit::bitfield(u32)] +pub struct Ier { + /// Enable Modem Status Interrupt + #[bit(3, rw)] + modem_status: bool, + /// Enable Receiver Line Status Interrupt + #[bit(2, rw)] + line_status: bool, + /// Enable Transmitter Holding Register Empty Interrupt + #[bit(1, rw)] + thr_empty: bool, + /// Enable Received Data Available Interrupt + #[bit(0, rw)] + rx_avl: bool, +} + +/// Interrupt identification ID +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +pub enum IntId2 { + ReceiverLineStatus = 0b011, + RxDataAvailable = 0b010, + CharTimeout = 0b110, + ThrEmpty = 0b001, + ModemStatus = 0b000, +} + +/// Interrupt Identification Register +#[bitbybit::bitfield(u32)] +pub struct Iir { + /// 16550 mode enabled? + #[bits(6..=7, r)] + fifo_enabled: u2, + #[bits(1..=3, r)] + int_id: Option, + /// Interrupt Pending, active low. + #[bit(0, r)] + int_pend_n: bool, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +pub enum RxFifoTrigger { + OneByte = 0b00, + FourBytes = 0b01, + EightBytes = 0b10, + FourteenBytes = 0b11, +} + +impl RxFifoTrigger { + pub const fn as_num(self) -> u32 { + match self { + RxFifoTrigger::OneByte => 1, + RxFifoTrigger::FourBytes => 4, + RxFifoTrigger::EightBytes => 8, + RxFifoTrigger::FourteenBytes => 14, + } + } +} + +/// FIFO Control Register +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct Fcr { + #[bits(4..=5, rw)] + rx_fifo_trigger: RxFifoTrigger, + #[bit(3, rw)] + dma_mode_sel: bool, + #[bit(2, rw)] + reset_tx_fifo: bool, + #[bit(1, rw)] + reset_rx_fifo: bool, + #[bit(0, rw)] + fifo_enable: bool, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +pub enum WordLen { + Five = 0b00, + Six = 0b01, + Seven = 0b10, + #[default] + Eight = 0b11, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Default, Debug, PartialEq, Eq)] +pub enum StopBits { + #[default] + One = 0b0, + /// 1.5 for 5 bits/char, 2 otherwise. + OnePointFiveOrTwo = 0b1, +} + +/// Line control register +#[bitbybit::bitfield(u32, default = 0x00)] +pub struct Lcr { + #[bit(7, rw)] + div_access_latch: bool, + #[bit(6, rw)] + set_break: bool, + #[bit(5, rw)] + stick_parity: bool, + #[bit(4, rw)] + even_parity: bool, + #[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, + #[bits(0..=1, rw)] + word_len: WordLen, +} + +impl Lcr { + pub fn new_for_divisor_access() -> Self { + Self::new_with_raw_value(0x80) + } +} + +/// Line Status Register +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct Lsr { + #[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. + #[bit(6, rw)] + tx_empty: bool, + /// In the FIFO mode, this is set to 1 when the TX FIFO is empty. There might still be a byte + /// in the TX shift register. + #[bit(5, rw)] + thr_empty: bool, + #[bit(4, rw)] + break_interrupt: bool, + #[bit(3, rw)] + framing_error: bool, + #[bit(2, rw)] + parity_error: bool, + #[bit(1, rw)] + overrun_error: bool, + #[bit(0, rw)] + data_ready: bool, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct AxiUart16550 { + _reserved: [u32; 0x400], + /// FIFO register for LCR[7] == 0 or Divisor Latch (LSB) register for LCR[7] == 1 + fifo_or_dll: u32, + /// Interrupt Enable Register for LCR[7] == 0 or Divisor Latch (MSB) register for LCR[7] == 1 + ier_or_dlm: u32, + /// Interrupt Identification Register or FIFO Control Register. FCR is not included in 16450 + /// mode. If LCR[7] == 1, this register will be the read-only FIFO control register. + /// If LCR[7] == 0, this register will be the read-only interrupt IIR register or the + /// write-only FIFO control register. + iir_or_fcr: u32, + /// Line Control Register + lcr: Lcr, + /// Modem Control Register + mcr: u32, + /// Line Status Register + lsr: Lsr, + /// Modem Status Register + msr: u32, + /// Scratch Register + scr: u32, +} diff --git a/src/rx.rs b/src/rx.rs new file mode 100644 index 0000000..e5047aa --- /dev/null +++ b/src/rx.rs @@ -0,0 +1,223 @@ +use core::convert::Infallible; + +use crate::{ + DEFAULT_RX_TRIGGER_LEVEL, + registers::{self, Fcr, Ier, Iir, IntId2, Lsr}, +}; + +#[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 { + /// Internal MMIO register structure. + pub(crate) regs: registers::MmioAxiUart16550<'static>, + pub(crate) errors: Option, +} + +impl Rx { + /// Steal the RX part of the UART 16550. + /// + /// You should only use this if you can not use the regular [super::AxiUart16550] constructor + /// and the [super::AxiUart16550::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::AxiUart16550::new] apply. + pub const unsafe fn steal(base_addr: usize) -> Self { + Self { + regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, + errors: None, + } + } + + pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { + Self { regs, errors: None } + } + + #[inline] + pub fn read_fifo(&mut self) -> nb::Result { + let status_reg = self.regs.read_lsr(); + if !status_reg.data_ready() { + return Err(nb::Error::WouldBlock); + } + if status_reg.error_in_rx_fifo() { + self.errors = Some(Self::lsr_to_errors(status_reg)); + } + Ok(self.read_fifo_unchecked()) + } + + #[inline(always)] + pub fn read_fifo_unchecked(&mut self) -> u8 { + self.regs.read_fifo_or_dll() as u8 + } + + /// Start interrupt driven reception. + /// + /// This function resets the FIFO with [Self::reset_fifo] and then enables the interrupts + /// with [Self::enable_interrupt]. + /// After this, you only need to call [Self::on_interrupt_receiver_line_status] and + /// [Self::on_interrupt_data_available_or_char_timeout] in your interrupt handler depending + /// on the value of the IIR register to continously receive data. + #[inline] + pub fn start_interrupt_driven_reception(&mut self) { + self.reset_fifo(); + self.enable_interrupt(); + } + + #[inline] + pub fn enable_interrupt(&mut self) { + self.regs.modify_ier_or_dlm(|val| { + let mut ier = Ier::new_with_raw_value(val); + ier.set_rx_avl(true); + ier.set_line_status(true); + ier.raw_value() + }); + } + + #[inline] + pub fn disable_interrupt(&mut self) { + self.regs.modify_ier_or_dlm(|val| { + let mut ier = Ier::new_with_raw_value(val); + ier.set_rx_avl(false); + ier.set_line_status(false); + ier.raw_value() + }); + } + + #[inline] + pub fn reset_fifo(&mut self) { + self.regs.write_iir_or_fcr( + Fcr::builder() + .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) + .with_dma_mode_sel(false) + .with_reset_tx_fifo(false) + .with_reset_rx_fifo(true) + .with_fifo_enable(true) + .build() + .raw_value(), + ); + } + + #[inline(always)] + pub fn has_data(&mut self) -> bool { + self.regs.read_lsr().data_ready() + } + + #[inline] + pub fn read_iir(&mut self) -> Iir { + Iir::new_with_raw_value(self.regs.read_iir_or_fcr()) + } + + #[inline] + pub fn on_interrupt_receiver_line_status(&mut self, _iir: Iir) -> RxErrors { + let lsr = self.regs.read_lsr(); + Self::lsr_to_errors(lsr) + } + + #[inline] + pub fn on_interrupt_data_available_or_char_timeout( + &mut self, + int_id2: IntId2, + 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()); + (0..trigger_level.rx_fifo_trigger().as_num() as usize).for_each(|i| { + buf[i] = self.read_fifo_unchecked(); + read += 1; + }); + } + // Read the rest of the FIFO. + while self.has_data() && read < 16 { + buf[read] = self.read_fifo_unchecked(); + read += 1; + } + read + } + + pub fn lsr_to_errors(status_reg: Lsr) -> RxErrors { + let mut errors = RxErrors::new(); + if status_reg.framing_error() { + errors.frame = true; + } + if status_reg.parity_error() { + errors.parity = true; + } + if status_reg.overrun_error() { + errors.overrun = true; + } + 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 { + 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 { + 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) + } +} diff --git a/src/tx.rs b/src/tx.rs new file mode 100644 index 0000000..d693fb8 --- /dev/null +++ b/src/tx.rs @@ -0,0 +1,152 @@ +use core::convert::Infallible; + +use crate::{ + DEFAULT_RX_TRIGGER_LEVEL, + registers::{self, Fcr, Ier}, +}; + +pub struct Tx { + /// Internal MMIO register structure. + pub(crate) regs: registers::MmioAxiUart16550<'static>, +} + +impl Tx { + /// Steal the TX part of the UART 16550. + /// + /// You should only use this if you can not use the regular [super::AxiUart16550] constructor + /// and the [super::AxiUart16550::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::AxiUart16550::new] apply. + pub const unsafe fn steal(base_addr: usize) -> Self { + Self { + regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, + } + } + + pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { + Self { regs } + } + + #[inline] + pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { + if !self.thr_empty() { + return Err(nb::Error::WouldBlock); + } + self.write_fifo_unchecked(data); + Ok(()) + } + + #[inline] + pub fn enable_interrupt(&mut self) { + self.regs.modify_ier_or_dlm(|val| { + let mut ier = Ier::new_with_raw_value(val); + ier.set_thr_empty(true); + ier.raw_value() + }); + } + + #[inline] + pub fn disable_interrupt(&mut self) { + self.regs.modify_ier_or_dlm(|val| { + let mut ier = Ier::new_with_raw_value(val); + ier.set_thr_empty(false); + ier.raw_value() + }); + } + + /// 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_fifo_or_dll(data as u32); + } + + // TODO: Make this non-mut as soon as pure reads are available. + #[inline(always)] + pub fn thr_empty(&mut self) -> bool { + self.regs.read_lsr().thr_empty() + } + + #[inline(always)] + pub fn tx_empty(&mut self) -> bool { + self.regs.read_lsr().tx_empty() + } + + #[inline] + pub fn reset_fifo(&mut self) { + self.regs.write_iir_or_fcr( + Fcr::builder() + .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) + .with_dma_mode_sel(false) + .with_reset_tx_fifo(true) + .with_reset_rx_fifo(false) + .with_fifo_enable(true) + .build() + .raw_value(), + ); + } + + #[inline] + pub fn on_interrupt_thr_empty(&mut self, next_write_chunk: &[u8]) -> usize { + if next_write_chunk.is_empty() { + return 0; + } + let mut written = 0; + while self.thr_empty() && written < next_write_chunk.len() { + self.write_fifo_unchecked(next_write_chunk[written]); + written += 1; + } + written + } +} + +impl embedded_hal_nb::serial::ErrorType for Tx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Write for Tx { + #[inline] + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.write_fifo(word) + } + + #[inline] + fn flush(&mut self) -> nb::Result<(), Self::Error> { + while !self.tx_empty() {} + Ok(()) + } +} + +impl embedded_io::ErrorType for Tx { + type Error = Infallible; +} + +impl embedded_io::Write for Tx { + fn write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + while !self.thr_empty() {} + 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.tx_empty() {} + Ok(()) + } +} diff --git a/src/tx_async.rs b/src/tx_async.rs new file mode 100644 index 0000000..b6dd8e0 --- /dev/null +++ b/src/tx_async.rs @@ -0,0 +1,259 @@ +//! # 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 embedded_hal_async::delay::DelayNs; +use raw_slice::RawBufSlice; + +use crate::{ + FIFO_DEPTH, Tx, + registers::{self, Ier}, +}; + +#[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>; 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(tx: &mut Tx, waker_slot: usize) { + if waker_slot >= NUM_WAKERS { + return; + } + let status = tx.regs.read_lsr(); + let ier = Ier::new_with_raw_value(tx.regs.read_ier_or_dlm()); + // Interrupt are not even enabled. + if !ier.thr_empty() { + 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(); + // We have to use the THRE instead of the TEMT status flag here, because the interrupt + // is configured to trigger on the THRE flag and the UART might still be busy shifting the + // last byte out. + if (context.progress >= slice_len && status.thr_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); + tx.disable_interrupt(); + 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 { + match tx.write_fifo(slice[context.progress]) { + Ok(_) => context.progress += 1, + Err(nb::Error::WouldBlock) => break, + } + } + // 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, + reg_block: registers::MmioAxiUart16550<'static>, +} + +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 { + TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed); + tx.disable_interrupt(); + tx.reset_fifo(); + + let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH); + 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; + }); + // We fill the FIFO with initial data. + for data in data.iter().take(init_fill_count) { + tx.write_fifo_unchecked(*data); + } + tx.enable_interrupt(); + Ok(Self { + waker_idx, + reg_block: unsafe { tx.regs.clone() }, + }) + } +} + +impl Future for TxFuture { + type Output = usize; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + 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) { + let mut tx = Tx::new(unsafe { self.reg_block.clone() }); + tx.disable_interrupt(); + } +} + +pub struct TxAsync { + tx: Tx, + waker_idx: usize, + delay: D, +} + +impl TxAsync { + /// Create a new asynchronous TX structure. + /// + /// The delay function is a [DelayNs] provider which is used to allow flushing the + /// device properly. This is because even when a write finished, the UART might still + /// be busy shifting the last byte out. + pub fn new(tx: Tx, waker_idx: usize, delay: D) -> Result { + if waker_idx >= NUM_WAKERS { + return Err(InvalidWakerIndex(waker_idx)); + } + Ok(Self { + tx, + waker_idx, + delay, + }) + } + + /// 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 + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) { + while !self.tx.tx_empty() { + self.delay.delay_us(10).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 { + Ok(self.write(buf).await) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await; + Ok(()) + } +}