From 265cd75fa7ffcab7c33c7c78e74b18b71fe3e7a5 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 24 Mar 2026 07:17:03 +0100 Subject: [PATCH 1/6] style: fix --- src/backend.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index a655aa7..e64a6e5 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -223,10 +223,10 @@ impl Backend for PioBackend { unsafe { let ret: u8; asm!( - "inb %dx, %al", - in("dx") port.0, - out("al") ret, - options(att_syntax, nostack, preserves_flags) + "inb %dx, %al", + in("dx") port.0, + out("al") ret, + options(att_syntax, nostack, preserves_flags) ); ret } @@ -237,10 +237,10 @@ impl Backend for PioBackend { // SAFETY: The caller ensured that the I/O port is safe to use. unsafe { asm!( - "outb %al, %dx", - in("al") value, - in("dx") port.0, - options(att_syntax, nostack, preserves_flags) + "outb %al, %dx", + in("al") value, + in("dx") port.0, + options(att_syntax, nostack, preserves_flags) ); } } From 1f47a915cec89b9394731eaf58ddc4d0876abf33 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 24 Mar 2026 07:17:38 +0100 Subject: [PATCH 2/6] crate: move backend module to directory --- src/{backend.rs => backend/mod.rs} | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) rename src/{backend.rs => backend/mod.rs} (94%) diff --git a/src/backend.rs b/src/backend/mod.rs similarity index 94% rename from src/backend.rs rename to src/backend/mod.rs index e64a6e5..7599ef9 100644 --- a/src/backend.rs +++ b/src/backend/mod.rs @@ -223,10 +223,10 @@ impl Backend for PioBackend { unsafe { let ret: u8; asm!( - "inb %dx, %al", - in("dx") port.0, - out("al") ret, - options(att_syntax, nostack, preserves_flags) + "inb %dx, %al", + in("dx") port.0, + out("al") ret, + options(att_syntax, nostack, preserves_flags) ); ret } @@ -237,10 +237,10 @@ impl Backend for PioBackend { // SAFETY: The caller ensured that the I/O port is safe to use. unsafe { asm!( - "outb %al, %dx", - in("al") value, - in("dx") port.0, - options(att_syntax, nostack, preserves_flags) + "outb %al, %dx", + in("al") value, + in("dx") port.0, + options(att_syntax, nostack, preserves_flags) ); } } @@ -265,10 +265,10 @@ mod arch { // SAFETY: Caller ensures the address is valid MMIO memory. unsafe { core::arch::asm!( - "ldrb {ret:w}, [{ptr}]", - ptr = in(reg) ptr, - ret = out(reg) ret, - options(nostack, preserves_flags) + "ldrb {ret:w}, [{ptr}]", + ptr = in(reg) ptr, + ret = out(reg) ret, + options(nostack, preserves_flags) ); } ret @@ -290,10 +290,10 @@ mod arch { // SAFETY: Caller ensures the address is valid MMIO memory. unsafe { core::arch::asm!( - "strb {val:w}, [{ptr}]", - val = in(reg) value, - ptr = in(reg) ptr, - options(nostack, preserves_flags) + "strb {val:w}, [{ptr}]", + val = in(reg) value, + ptr = in(reg) ptr, + options(nostack, preserves_flags) ); } } From b232b2c6088e6b8ce0e69469684adc1eb44ed9bf Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 24 Mar 2026 07:21:49 +0100 Subject: [PATCH 3/6] crate: split backend implementations into modules This helps to reduce #[cfg()] duplication. --- src/backend/mmio.rs | 141 ++++++++++++++++++++++++++++ src/backend/mod.rs | 218 ++------------------------------------------ src/backend/pio.rs | 71 +++++++++++++++ 3 files changed, 220 insertions(+), 210 deletions(-) create mode 100644 src/backend/mmio.rs create mode 100644 src/backend/pio.rs diff --git a/src/backend/mmio.rs b/src/backend/mmio.rs new file mode 100644 index 0000000..1c0f2b8 --- /dev/null +++ b/src/backend/mmio.rs @@ -0,0 +1,141 @@ +//! MMIO backend implementation. + +use super::{Backend, RegisterAddress, private}; +use crate::spec::NUM_REGISTERS; +use core::num::NonZeroU8; +use core::ptr::NonNull; + +/// Memory-mapped I/O (MMIO) address. +/// +/// Guaranteed to be not null. +/// +/// See [`RegisterAddress`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] +pub struct MmioAddress(pub(crate) NonNull); + +// SAFETY: `Uart16550` is not `Sync`, so concurrent access from multiple +// threads is not possible through this type's API alone. Implementing `Send` +// allows moving ownership to another thread, which is safe because at any +// point only one thread holds the `&mut self` required for all operations. +// Without this, higher-level wrappers such as `Mutex` could not +// be constructed, since `Mutex: Sync` requires `T: Send`. +unsafe impl Send for MmioAddress {} + +impl RegisterAddress for MmioAddress { + #[inline(always)] + fn add_offset(self, offset: u8) -> Self { + // SAFETY: We ensure on a higher level that the base address is valid + // and that this will not wrap. + let address = unsafe { self.0.add(offset as usize) }; + Self(address) + } +} + +impl private::Sealed for MmioAddress {} + +/// Arch-specific quirks to access hardware. +/// +/// On MMIO-access on aarch64, LLVM may emit instructions that are not properly +/// virtualizable. We therefore need to be more explicit about the instruction. +/// More info: +mod arch { + use super::MmioAddress; + #[cfg(any(doc, not(target_arch = "aarch64")))] + use core::ptr; + + /// Wrapper around [`ptr::read_volatile`]. + #[cfg(target_arch = "aarch64")] + #[inline(always)] + pub unsafe fn mmio_read_register(address: MmioAddress) -> u8 { + let ptr = address.0.as_ptr(); + let ret: u8; + // SAFETY: Caller ensures the address is valid MMIO memory. + unsafe { + core::arch::asm!( + "ldrb {ret:w}, [{ptr}]", + ptr = in(reg) ptr, + ret = out(reg) ret, + options(nostack, preserves_flags) + ); + } + ret + } + + /// Wrapper around [`ptr::read_volatile`]. + #[cfg(not(target_arch = "aarch64"))] + #[inline(always)] + pub unsafe fn mmio_read_register(address: MmioAddress) -> u8 { + // SAFETY: Caller ensures the address is valid MMIO memory. + unsafe { ptr::read_volatile(address.0.as_ptr()) } + } + + /// Wrapper around [`ptr::write_volatile`]. + #[cfg(target_arch = "aarch64")] + #[inline(always)] + pub unsafe fn mmio_write_register(address: MmioAddress, value: u8) { + let ptr = address.0.as_ptr(); + // SAFETY: Caller ensures the address is valid MMIO memory. + unsafe { + core::arch::asm!( + "strb {val:w}, [{ptr}]", + val = in(reg) value, + ptr = in(reg) ptr, + options(nostack, preserves_flags) + ); + } + } + + /// Wrapper around [`ptr::write_volatile`]. + #[cfg(not(target_arch = "aarch64"))] + #[inline(always)] + pub unsafe fn mmio_write_register(address: MmioAddress, value: u8) { + // SAFETY: Caller ensures the address is valid MMIO memory. + unsafe { ptr::write_volatile(address.0.as_ptr(), value) } + } +} + +/// MMIO-mapped UART 16550. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] +pub struct MmioBackend { + // non-null + pub(crate) base_address: MmioAddress, + pub(crate) stride: NonZeroU8, +} + +impl private::Sealed for MmioBackend {} + +impl Backend for MmioBackend { + type Address = MmioAddress; + + #[inline(always)] + fn base(&self) -> Self::Address { + self.base_address + } + + #[inline(always)] + fn stride(&self) -> NonZeroU8 { + self.stride + } + + #[inline(always)] + unsafe fn _read_register(&mut self, address: MmioAddress) -> u8 { + debug_assert!(address >= self.base()); + let upper_bound_incl = (NUM_REGISTERS - 1) * usize::from(u8::from(self.stride)); + // Address is in the device's address range + debug_assert!(address.0.as_ptr() <= self.base().0.as_ptr().wrapping_add(upper_bound_incl)); + + // SAFETY: The caller ensured that the MMIO address is safe to use. + unsafe { arch::mmio_read_register(address) } + } + + #[inline(always)] + unsafe fn _write_register(&mut self, address: MmioAddress, value: u8) { + debug_assert!(address >= self.base()); + let upper_bound_incl = (NUM_REGISTERS - 1) * usize::from(u8::from(self.stride)); + // Address is in the device's address range + debug_assert!(address.0.as_ptr() <= self.base().0.as_ptr().wrapping_add(upper_bound_incl)); + + // SAFETY: The caller ensured that the MMIO address is safe to use. + unsafe { arch::mmio_write_register(address, value) } + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 7599ef9..b196ff3 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,12 +7,17 @@ //! - [`PioBackend`] //! - [`MmioBackend`] -use crate::spec::NUM_REGISTERS; +mod mmio; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod pio; + +pub use mmio::{MmioAddress, MmioBackend}; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use core::arch::asm; +pub use pio::{PioBackend, PortIoAddress}; + +use crate::spec::NUM_REGISTERS; use core::fmt::Debug; use core::num::NonZeroU8; -use core::ptr::NonNull; mod private { pub trait Sealed {} @@ -31,53 +36,6 @@ pub trait RegisterAddress: Copy + Clone + Debug + Sized + private::Sealed { fn add_offset(self, offset: u8) -> Self; } -/// x86 port I/O address. -/// -/// See [`RegisterAddress`]. -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] -pub struct PortIoAddress(pub(crate) u16); - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -impl RegisterAddress for PortIoAddress { - #[inline(always)] - fn add_offset(self, offset: u8) -> Self { - let port = self.0 + offset as u16; - Self(port) - } -} - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -impl private::Sealed for PortIoAddress {} - -/// Memory-mapped I/O (MMIO) address. -/// -/// Guaranteed to be not null. -/// -/// See [`RegisterAddress`]. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] -pub struct MmioAddress(pub(crate) NonNull); - -// SAFETY: `Uart16550` is not `Sync`, so concurrent access from multiple -// threads is not possible through this type's API alone. Implementing `Send` -// allows moving ownership to another thread, which is safe because at any -// point only one thread holds the `&mut self` required for all operations. -// Without this, higher-level wrappers such as `Mutex` could not -// be constructed, since `Mutex: Sync` requires `T: Send`. -unsafe impl Send for MmioAddress {} - -impl RegisterAddress for MmioAddress { - #[inline(always)] - fn add_offset(self, offset: u8) -> Self { - // SAFETY: We ensure on a higher level that the base address is valid - // and that this will not wrap. - let address = unsafe { self.0.add(offset as usize) }; - Self(address) - } -} - -impl private::Sealed for MmioAddress {} - #[track_caller] fn assert_offset(offset: u8) { assert!( @@ -192,163 +150,3 @@ pub trait Backend: Send + private::Sealed { #[doc(hidden)] unsafe fn _write_register(&mut self, address: Self::Address, value: u8); } - -/// x86 Port I/O backed UART 16550. -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] -pub struct PioBackend(pub(crate) PortIoAddress /* base port */); - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -impl private::Sealed for PioBackend {} - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -impl Backend for PioBackend { - type Address = PortIoAddress; - - #[inline(always)] - fn base(&self) -> Self::Address { - self.0 - } - - #[inline(always)] - fn stride(&self) -> NonZeroU8 { - // stride=1: x86 port I/O registers are always at consecutive port - // numbers. Compiler optimizes the unwrap away. - NonZeroU8::new(1).unwrap() - } - - #[inline(always)] - unsafe fn _read_register(&mut self, port: PortIoAddress) -> u8 { - // SAFETY: The caller ensured that the I/O port is safe to use. - unsafe { - let ret: u8; - asm!( - "inb %dx, %al", - in("dx") port.0, - out("al") ret, - options(att_syntax, nostack, preserves_flags) - ); - ret - } - } - - #[inline(always)] - unsafe fn _write_register(&mut self, port: PortIoAddress, value: u8) { - // SAFETY: The caller ensured that the I/O port is safe to use. - unsafe { - asm!( - "outb %al, %dx", - in("al") value, - in("dx") port.0, - options(att_syntax, nostack, preserves_flags) - ); - } - } -} - -/// Arch-specific quirks to access hardware. -/// -/// On MMIO-access on aarch64, LLVM may emit instructions that are not properly -/// virtualizable. We therefore need to be more explicit about the instruction. -/// More info: -mod arch { - use crate::backend::MmioAddress; - #[cfg(any(doc, not(target_arch = "aarch64")))] - use core::ptr; - - /// Wrapper around [`ptr::read_volatile`]. - #[cfg(target_arch = "aarch64")] - #[inline(always)] - pub unsafe fn mmio_read_register(address: MmioAddress) -> u8 { - let ptr = address.0.as_ptr(); - let ret: u8; - // SAFETY: Caller ensures the address is valid MMIO memory. - unsafe { - core::arch::asm!( - "ldrb {ret:w}, [{ptr}]", - ptr = in(reg) ptr, - ret = out(reg) ret, - options(nostack, preserves_flags) - ); - } - ret - } - - /// Wrapper around [`ptr::read_volatile`]. - #[cfg(not(target_arch = "aarch64"))] - #[inline(always)] - pub unsafe fn mmio_read_register(address: MmioAddress) -> u8 { - // SAFETY: Caller ensures the address is valid MMIO memory. - unsafe { ptr::read_volatile(address.0.as_ptr()) } - } - - /// Wrapper around [`ptr::write_volatile`]. - #[cfg(target_arch = "aarch64")] - #[inline(always)] - pub unsafe fn mmio_write_register(address: MmioAddress, value: u8) { - let ptr = address.0.as_ptr(); - // SAFETY: Caller ensures the address is valid MMIO memory. - unsafe { - core::arch::asm!( - "strb {val:w}, [{ptr}]", - val = in(reg) value, - ptr = in(reg) ptr, - options(nostack, preserves_flags) - ); - } - } - - /// Wrapper around [`ptr::write_volatile`]. - #[cfg(not(target_arch = "aarch64"))] - #[inline(always)] - pub unsafe fn mmio_write_register(address: MmioAddress, value: u8) { - // SAFETY: Caller ensures the address is valid MMIO memory. - unsafe { ptr::write_volatile(address.0.as_ptr(), value) } - } -} - -/// MMIO-mapped UART 16550. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] -pub struct MmioBackend { - // non-null - pub(crate) base_address: MmioAddress, - pub(crate) stride: NonZeroU8, -} - -impl private::Sealed for MmioBackend {} - -impl Backend for MmioBackend { - type Address = MmioAddress; - - #[inline(always)] - fn base(&self) -> Self::Address { - self.base_address - } - - #[inline(always)] - fn stride(&self) -> NonZeroU8 { - self.stride - } - - #[inline(always)] - unsafe fn _read_register(&mut self, address: MmioAddress) -> u8 { - debug_assert!(address >= self.base()); - let upper_bound_incl = (NUM_REGISTERS - 1) * usize::from(u8::from(self.stride)); - // Address is in the device's address range - debug_assert!(address.0.as_ptr() <= self.base().0.as_ptr().wrapping_add(upper_bound_incl)); - - // SAFETY: The caller ensured that the MMIO address is safe to use. - unsafe { arch::mmio_read_register(address) } - } - - #[inline(always)] - unsafe fn _write_register(&mut self, address: MmioAddress, value: u8) { - debug_assert!(address >= self.base()); - let upper_bound_incl = (NUM_REGISTERS - 1) * usize::from(u8::from(self.stride)); - // Address is in the device's address range - debug_assert!(address.0.as_ptr() <= self.base().0.as_ptr().wrapping_add(upper_bound_incl)); - - // SAFETY: The caller ensured that the MMIO address is safe to use. - unsafe { arch::mmio_write_register(address, value) } - } -} diff --git a/src/backend/pio.rs b/src/backend/pio.rs new file mode 100644 index 0000000..c43c6d1 --- /dev/null +++ b/src/backend/pio.rs @@ -0,0 +1,71 @@ +//! x86 Port IO backend implementation. + +use super::{Backend, RegisterAddress, private}; +use core::arch::asm; +use core::num::NonZeroU8; + +/// x86 port I/O address. +/// +/// See [`RegisterAddress`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] +pub struct PortIoAddress(pub(crate) u16); + +impl RegisterAddress for PortIoAddress { + #[inline(always)] + fn add_offset(self, offset: u8) -> Self { + let port = self.0 + offset as u16; + Self(port) + } +} + +impl private::Sealed for PortIoAddress {} + +/// x86 Port I/O backed UART 16550. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Hash)] +pub struct PioBackend(pub(crate) PortIoAddress /* base port */); + +impl private::Sealed for PioBackend {} + +impl Backend for PioBackend { + type Address = PortIoAddress; + + #[inline(always)] + fn base(&self) -> Self::Address { + self.0 + } + + #[inline(always)] + fn stride(&self) -> NonZeroU8 { + // stride=1: x86 port I/O registers are always at consecutive port + // numbers. Compiler optimizes the unwrap away. + NonZeroU8::new(1).unwrap() + } + + #[inline(always)] + unsafe fn _read_register(&mut self, port: PortIoAddress) -> u8 { + // SAFETY: The caller ensured that the I/O port is safe to use. + unsafe { + let ret: u8; + asm!( + "inb %dx, %al", + in("dx") port.0, + out("al") ret, + options(att_syntax, nostack, preserves_flags) + ); + ret + } + } + + #[inline(always)] + unsafe fn _write_register(&mut self, port: PortIoAddress, value: u8) { + // SAFETY: The caller ensured that the I/O port is safe to use. + unsafe { + asm!( + "outb %al, %dx", + in("al") value, + in("dx") port.0, + options(att_syntax, nostack, preserves_flags) + ); + } + } +} From 347088cad9e77416c7885e26e1d2de347140746d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 23 Mar 2026 21:10:09 +0100 Subject: [PATCH 4/6] doc: add support for rustdocs' doc_cfg feature - cfg badges per item - needs nightly - comes with auto-detection of cfg items, so there is no need for `#[doc(cfg(...))]` per item To verify, run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --no-deps --- .github/workflows/rust.yml | 5 +++++ Cargo.toml | 4 ++++ src/lib.rs | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aab3e21..1f9c5a9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -92,6 +92,7 @@ jobs: - run: rustup component add miri - run: cargo miri test --tests + # Style + documentation checks style_checks: runs-on: ubuntu-latest strategy: @@ -113,3 +114,7 @@ jobs: run: cargo clippy --all-targets - name: Rustdoc run: cargo doc --no-deps --document-private-items + - name: Rustdoc (doc_cfg + nightly) + env: + RUSTDOCFLAGS: --cfg docsrs + run: cargo +nightly doc --no-deps --document-private-items diff --git a/Cargo.toml b/Cargo.toml index 0362c6a..5a924ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,7 @@ assert2 = { version = "0.4.0", default-features = false, features = [] } [features] default = [] embedded-io = ["dep:embedded-io"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/lib.rs b/src/lib.rs index c24bf9f..366ac6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 +// Automatically add badges for cfg feature gates in documentation. +// https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html +#![cfg_attr(docsrs, feature(doc_cfg))] + //! # uart_16550 //! //! Simple yet highly configurable low-level driver for From 2b86ba73959e23273a6a09a7b328d77c54ef1e51 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 23 Mar 2026 20:43:23 +0100 Subject: [PATCH 5/6] ci: test cargo doc on multiple architectures --- .github/workflows/rust.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1f9c5a9..faf0e21 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -94,9 +94,13 @@ jobs: # Style + documentation checks style_checks: - runs-on: ubuntu-latest + runs-on: "${{ matrix.runs-on }}" strategy: matrix: + runs-on: + - macos-latest # aarch64 + - ubuntu-latest # x86_64 + - windows-latest # x86_64 rust: - stable steps: From 8893849bb3abb1244c68a512357b0a255ebc2adb Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Mon, 23 Mar 2026 20:43:03 +0100 Subject: [PATCH 6/6] doc: improve examples and wording + build on non-x86 - dedicated example for x86 port IO and MMIO - also builds on non-x86 systems To test doc cross-build, you may use RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --no-deps --document-private-items --target aarch64-unknown-linux-gnu -Zbuild-std --- README.md | 22 ++++++++++--- src/backend/mod.rs | 4 +-- src/lib.rs | 78 ++++++++++++++++++++++++++++++++++------------ src/tty.rs | 36 ++++++++++++++++----- 4 files changed, 107 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 4d59792..5b07568 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,24 @@ layer. # Overview -Use `Uart16550Tty` for a quick start. For more fine-grained low-level -control, please have a look at `Uart16550` instead. +Use `Uart16550Tty` for a quick start to write to a terminal via your serial +connection. For more fine-grained low-level control, please have a look at +`Uart16550` instead. -# Example (Minimalistic) +# Example (Minimal - x86 Port IO) + +```rust +use uart_16550::{Config, Uart16550Tty}; +use core::fmt::Write; + +fn main() { + // SAFETY: The port is valid and we have exclusive access. + let mut uart = unsafe { Uart16550Tty::new_port(0x3f8, Config::default()).expect("should initialize device") }; + uart.write_str("hello world\nhow's it going?"); +} +``` + +# Example (Minimal - MMIO) ```rust use uart_16550::{Config, Uart16550Tty}; @@ -58,7 +72,6 @@ use core::fmt::Write; fn main() { // SAFETY: The address is valid and we have exclusive access. let mut uart = unsafe { Uart16550Tty::new_mmio(0x1000 as *mut _, 4, Config::default()).expect("should initialize device") }; - // ^ or `new_port(0x3f8, Config::default())` uart.write_str("hello world\nhow's it going?"); } ``` @@ -74,6 +87,7 @@ fn main() { // ^ or `new_port(0x3f8)` uart.init(Config::default()).expect("should init device successfully"); uart.test_loopback().expect("should have working loopback mode"); + // Note: Might fail on real hardware with some null-modem cables uart.check_connected().expect("should have physically connected receiver"); uart.send_bytes_exact(b"hello world!"); } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index b196ff3..39fcf6c 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -8,11 +8,11 @@ //! - [`MmioBackend`] mod mmio; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] mod pio; pub use mmio::{MmioAddress, MmioBackend}; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] pub use pio::{PioBackend, PortIoAddress}; use crate::spec::NUM_REGISTERS; diff --git a/src/lib.rs b/src/lib.rs index 366ac6f..0f29a3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,24 +50,41 @@ //! //! # Overview //! -//! Use [`Uart16550Tty`] for a quick start. For more fine-grained low-level -//! control, please have a look at [`Uart16550`] instead. +//! Use [`Uart16550Tty`] for a quick start to write to a terminal via your +//! serial connection. For more fine-grained low-level control, please have a +//! look at [`Uart16550`] instead. The following examples show usage of the +//! latter. //! -//! # Example (Minimalistic) +//! # Example (Minimal - x86 Port IO) +//! +#![cfg_attr( + any(target_arch = "x86", target_arch = "x86_64"), + doc = "```rust,no_run" +)] +#![cfg_attr( + not(any(target_arch = "x86", target_arch = "x86_64")), + doc = "```rust,ignore" +)] +//! use uart_16550::{Config, Uart16550}; +//! +//! // SAFETY: The port is valid and we have exclusive access. +//! let mut uart = unsafe { Uart16550::new_port(0x3f8).unwrap() }; +//! uart.init(Config::default()).expect("should init device successfully"); +//! uart.send_bytes_exact(b"hello world!"); +//! ``` +//! +//! # Example (Minimal - MMIO) //! //! ```rust,no_run -//! use uart_16550::{Config, Uart16550Tty}; -//! use core::fmt::Write; +//! use uart_16550::{Config, Uart16550}; //! //! // SAFETY: The address is valid and we have exclusive access. -//! let mut uart = unsafe { Uart16550Tty::new_mmio(0x1000 as *mut _, 4, Config::default()).expect("should initialize device") }; -//! // ^ or `new_port(0x3f8, Config::default())` -//! uart.write_str("hello world\nhow's it going?"); +//! let mut uart = unsafe { Uart16550::new_mmio(0x1000 as *mut _, 4).unwrap() }; +//! uart.init(Config::default()).expect("should init device successfully"); +//! uart.send_bytes_exact(b"hello world!"); //! ``` //! -//! See [`Uart16550Tty`] for more details. -//! -//! # Example (More low-level control) +//! # Example (Recommended) //! //! ```rust,no_run //! use uart_16550::{Config, Uart16550}; @@ -77,6 +94,7 @@ //! // ^ or `new_port(0x3f8)` //! uart.init(Config::default()).expect("should init device successfully"); //! uart.test_loopback().expect("should have working loopback mode"); +//! // Note: Might fail on real hardware with some null-modem cables //! uart.check_connected().expect("should have physically connected receiver"); //! uart.send_bytes_exact(b"hello world!"); //! ``` @@ -146,7 +164,7 @@ pub use crate::error::*; pub use crate::tty::*; use crate::backend::{Backend, MmioAddress, MmioBackend}; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] use crate::backend::{PioBackend, PortIoAddress}; use crate::spec::registers::{DLL, DLM, FCR, IER, ISR, LCR, LSR, MCR, MSR, SPR, offsets}; use crate::spec::{FIFO_SIZE, NUM_REGISTERS, calc_baud_rate, calc_divisor}; @@ -171,16 +189,34 @@ mod tty; /// on the underlying hardware. /// /// This type is generic over x86 port I/O and MMIO via the corresponding -/// constructors (`Uart16550::new_port()` and [`Uart16550::new_mmio()`]. +/// constructors [`Uart16550::new_port()`] and [`Uart16550::new_mmio()`]. +/// The following examples show usage of the latter. +/// +/// # Example (Minimal - x86 Port IO) +/// +#[cfg_attr( + any(target_arch = "x86", target_arch = "x86_64"), + doc = "```rust,no_run" +)] +#[cfg_attr( + not(any(target_arch = "x86", target_arch = "x86_64")), + doc = "```rust,ignore" +)] +/// use uart_16550::{Config, Uart16550}; /// -/// # Example (Minimal) +/// // SAFETY: The port is valid and we have exclusive access. +/// let mut uart = unsafe { Uart16550::new_port(0x3f8).unwrap() }; +/// uart.init(Config::default()).expect("should init device successfully"); +/// uart.send_bytes_exact(b"hello world!"); +/// ``` +/// +/// # Example (Minimal - MMIO) /// /// ```rust,no_run /// use uart_16550::{Config, Uart16550}; /// /// // SAFETY: The address is valid and we have exclusive access. /// let mut uart = unsafe { Uart16550::new_mmio(0x1000 as *mut _, 4).unwrap() }; -/// // ^ or `new_port(0x3f8)` /// uart.init(Config::default()).expect("should init device successfully"); /// uart.send_bytes_exact(b"hello world!"); /// ``` @@ -195,6 +231,7 @@ mod tty; /// // ^ or `new_port(0x3f8)` /// uart.init(Config::default()).expect("should init device successfully"); /// uart.test_loopback().expect("should have working loopback mode"); +/// // Note: Might fail on real hardware with some null-modem cables /// uart.check_connected().expect("should have physically connected receiver"); /// uart.send_bytes_exact(b"hello world!"); /// ``` @@ -221,15 +258,16 @@ mod tty; /// - [`Uart16550::send_bytes_exact`]: Transmit all bytes, looping until the /// entire buffer has been written. /// - [`Uart16550::receive_bytes_exact`]: Receive bytes until the provided -/// buffer is completely filled. +/// buffer is filled. /// /// These methods spin until completion. /// /// # MMIO and Port I/O /// /// Uart 16550 devices are typically mapped via port I/O on x86 and via MMIO on -/// other platforms. The constructors `new_port()` and `new_mmio()` create an -/// instance of a device with the corresponding backend. +/// other platforms. The constructors [`Uart16550::new_port()`] and +/// [`Uart16550::new_mmio()`] create an instance of a device with the +/// corresponding backend. /// /// # Hints for Usage on Real Hardware /// @@ -248,7 +286,7 @@ pub struct Uart16550 { config: Config, } -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] impl Uart16550 { /// Creates a new [`Uart16550`] backed by x86 port I/O. /// @@ -955,7 +993,7 @@ mod tests { #[test] fn constructors() { // SAFETY: We just test the constructor but do not access the device. - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + #[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] unsafe { assert2::assert!(let Ok(_) = Uart16550::new_port(0x3f8)); assert2::assert!(let Ok(_) = Uart16550::new_port(u16::MAX - NUM_REGISTERS as u16)); diff --git a/src/tty.rs b/src/tty.rs index f47543b..056f0ca 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -12,13 +12,13 @@ //! See [`Uart16550Tty`]. use crate::backend::{Backend, MmioAddress, MmioBackend, RegisterAddress}; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] use crate::backend::{PioBackend, PortIoAddress}; use crate::{Config, InitError, InvalidAddressError, LoopbackError, Uart16550}; use core::error::Error; use core::fmt::{self, Display, Formatter}; -/// Errors that [`Uart16550Tty::new_port`] and [`Uart16550Tty::new_mmio`] may +/// Errors that [`Uart16550Tty::new_port()`] and [`Uart16550Tty::new_mmio()`] may /// return. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Uart16550TtyError { @@ -62,22 +62,44 @@ impl Error for Uart16550TtyError { /// Ideal for quickly observing debug output during VM development and testing. /// It implements [`fmt::Write`] allowing the use of `write!()`. /// -/// # Example +/// Access to the underlying UART device is provided through via +/// [`Uart16550Tty::inner()`] and [`Uart16550Tty::inner_mut()`]. +/// +/// # Example (x86 Port IO) +/// +#[cfg_attr( + any(target_arch = "x86", target_arch = "x86_64"), + doc = "```rust,no_run" +)] +#[cfg_attr( + not(any(target_arch = "x86", target_arch = "x86_64")), + doc = "```rust,ignore" +)] +/// use uart_16550::{Config, Uart16550Tty}; +/// use core::fmt::Write; +/// +/// // SAFETY: The port is valid and we have exclusive access. +/// let mut uart = unsafe { Uart16550Tty::new_port(0x3f8, Config::default()).expect("should initialize device") }; +/// uart.write_str("hello world\nhow's it going?"); +/// ``` +/// +/// # Example (MMIO) +/// /// ```rust,no_run /// use uart_16550::{Config, Uart16550Tty}; /// use core::fmt::Write; /// /// // SAFETY: The address is valid and we have exclusive access. /// let mut uart = unsafe { Uart16550Tty::new_mmio(0x1000 as *mut _, 4, Config::default()).expect("should initialize device") }; -/// // ^ or `new_port(0x3f8, Config::default())` /// uart.write_str("hello world\nhow's it going?"); /// ``` /// /// # MMIO and Port I/O /// /// Uart 16550 devices are typically mapped via port I/O on x86 and via MMIO on -/// other platforms. The constructors `new_port()` and `new_mmio()` create an -/// instance of a device with the corresponding backend. +/// other platforms. The constructors [`Uart16550Tty::new_port()`] and +/// [`Uart16550Tty::new_mmio()`] create an instance of a device with the +/// corresponding backend. /// /// # Hints for Usage on Real Hardware /// @@ -89,7 +111,7 @@ impl Error for Uart16550TtyError { #[derive(Debug)] pub struct Uart16550Tty(Uart16550); -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64", doc))] impl Uart16550Tty { /// Creates a new [`Uart16550Tty`] backed by x86 port I/O. ///