diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aab3e21..faf0e21 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -92,10 +92,15 @@ jobs: - run: rustup component add miri - run: cargo miri test --tests + # 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: @@ -113,3 +118,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/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.rs b/src/backend.rs deleted file mode 100644 index a655aa7..0000000 --- a/src/backend.rs +++ /dev/null @@ -1,354 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -//! Abstraction over the I/O backend (Hardware Abstraction Layer (HAL)). -//! -//! Main exports: -//! - [`Backend`] -//! - [`PioBackend`] -//! - [`MmioBackend`] - -use crate::spec::NUM_REGISTERS; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use core::arch::asm; -use core::fmt::Debug; -use core::num::NonZeroU8; -use core::ptr::NonNull; - -mod private { - pub trait Sealed {} -} - -/// Abstraction over register addresses in [`Backend`]. -/// -/// # Safety -/// -/// All implementations and instances of this trait are created within this -/// crate and do follow all safety invariants. API users don't get access to the -/// underlying register addresses, nor can they construct one themselves, as this -/// type et al. are sealed. -pub trait RegisterAddress: Copy + Clone + Debug + Sized + private::Sealed { - /// Adds a byte offset onto the base register address. - 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!( - offset < NUM_REGISTERS as u8, - "the offset should be within the expected range: expected {offset} to be less than {NUM_REGISTERS}", - ); -} - -/// Abstraction over the I/O backend of a UART 16550 microcontroller. -/// -/// This acts as Hardware Abstraction Layer (HAL) and abstracts over x86 port -/// I/O and generic MMIO. -/// -/// Users should use [`Backend::read`] and [`Backend::write`]. -pub trait Backend: Send + private::Sealed { - /// The [`RegisterAddress`] that naturally belongs to the [`Backend`]. - type Address: RegisterAddress; - - /* convenience with default impl */ - - /// Reads one byte from the specified register at the given offset. - /// - /// This needs a mutable reference as reads can have side effects on the - /// device, depending on the register. - /// - /// # Arguments - /// - /// - `offset`: The register offset regarding the base register. The offset - /// **must** be less than [`NUM_REGISTERS`]. - /// - /// # Safety - /// - /// Callers must ensure that the effective address consisting of - /// [`Self::base`] and `offset` is valid and safe to read. - #[inline(always)] - unsafe fn read(&mut self, offset: u8) -> u8 { - assert_offset(offset); - let address_offset = offset - .checked_mul(u8::from(self.stride())) - .expect("offset * stride overflows u8; reduce stride"); - let addr = self.base().add_offset(address_offset); - // SAFETY: The caller ensured that the register address is safe to use. - unsafe { self._read_register(addr) } - } - - /// Writes one byte to the specified register at the given offset. - /// - /// Writes can have side effects on the device, depending on the register. - /// - /// # Arguments - /// - /// - `offset`: The register offset regarding the base register. The offset - /// **must** be less than [`NUM_REGISTERS`]. - /// - /// # Safety - /// - /// Callers must ensure that the effective address consisting of - /// [`Self::base`] and `offset` is valid and safe to write. - #[inline(always)] - unsafe fn write(&mut self, offset: u8, value: u8) { - assert_offset(offset); - let address_offset = offset - .checked_mul(u8::from(self.stride())) - .expect("offset * stride overflows u8; reduce stride"); - let addr = self.base().add_offset(address_offset); - // SAFETY: The caller ensured that the register address is safe to use. - unsafe { self._write_register(addr, value) } - } - - /* needs impl */ - - /// Returns the base [`RegisterAddress`]. - fn base(&self) -> Self::Address; - - /// Returns the configured stride. - /// - /// The stride is the fixed byte distance in physical address space between - /// consecutive logical registers, i.e. how much the address increases when - /// moving from one register index to the next. - fn stride(&self) -> NonZeroU8; - - /// PRIVATE API! Use [`Self::read`]! - /// - /// Reads one byte from the specified register. - /// - /// This needs a mutable reference as reads can have side effects on the - /// device, depending on the register. - /// - /// # Arguments - /// - /// - `address`: The total address of the register. - /// - /// # Safety - /// - /// Callers must ensure that the provided address is valid and safe to read. - #[doc(hidden)] - unsafe fn _read_register(&mut self, address: Self::Address) -> u8; - - /// PRIVATE API! Use [`Self::write`]! - /// - /// Writes one byte to the specified register. - /// - /// Writes can have side effects on the device, depending on the register. - /// - /// # Arguments - /// - /// - `address`: The total address of the register. - /// - /// # Safety - /// - /// Callers must ensure that the provided address is valid and safe to write. - #[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/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 new file mode 100644 index 0000000..39fcf6c --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Abstraction over the I/O backend (Hardware Abstraction Layer (HAL)). +//! +//! Main exports: +//! - [`Backend`] +//! - [`PioBackend`] +//! - [`MmioBackend`] + +mod mmio; +#[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", doc))] +pub use pio::{PioBackend, PortIoAddress}; + +use crate::spec::NUM_REGISTERS; +use core::fmt::Debug; +use core::num::NonZeroU8; + +mod private { + pub trait Sealed {} +} + +/// Abstraction over register addresses in [`Backend`]. +/// +/// # Safety +/// +/// All implementations and instances of this trait are created within this +/// crate and do follow all safety invariants. API users don't get access to the +/// underlying register addresses, nor can they construct one themselves, as this +/// type et al. are sealed. +pub trait RegisterAddress: Copy + Clone + Debug + Sized + private::Sealed { + /// Adds a byte offset onto the base register address. + fn add_offset(self, offset: u8) -> Self; +} + +#[track_caller] +fn assert_offset(offset: u8) { + assert!( + offset < NUM_REGISTERS as u8, + "the offset should be within the expected range: expected {offset} to be less than {NUM_REGISTERS}", + ); +} + +/// Abstraction over the I/O backend of a UART 16550 microcontroller. +/// +/// This acts as Hardware Abstraction Layer (HAL) and abstracts over x86 port +/// I/O and generic MMIO. +/// +/// Users should use [`Backend::read`] and [`Backend::write`]. +pub trait Backend: Send + private::Sealed { + /// The [`RegisterAddress`] that naturally belongs to the [`Backend`]. + type Address: RegisterAddress; + + /* convenience with default impl */ + + /// Reads one byte from the specified register at the given offset. + /// + /// This needs a mutable reference as reads can have side effects on the + /// device, depending on the register. + /// + /// # Arguments + /// + /// - `offset`: The register offset regarding the base register. The offset + /// **must** be less than [`NUM_REGISTERS`]. + /// + /// # Safety + /// + /// Callers must ensure that the effective address consisting of + /// [`Self::base`] and `offset` is valid and safe to read. + #[inline(always)] + unsafe fn read(&mut self, offset: u8) -> u8 { + assert_offset(offset); + let address_offset = offset + .checked_mul(u8::from(self.stride())) + .expect("offset * stride overflows u8; reduce stride"); + let addr = self.base().add_offset(address_offset); + // SAFETY: The caller ensured that the register address is safe to use. + unsafe { self._read_register(addr) } + } + + /// Writes one byte to the specified register at the given offset. + /// + /// Writes can have side effects on the device, depending on the register. + /// + /// # Arguments + /// + /// - `offset`: The register offset regarding the base register. The offset + /// **must** be less than [`NUM_REGISTERS`]. + /// + /// # Safety + /// + /// Callers must ensure that the effective address consisting of + /// [`Self::base`] and `offset` is valid and safe to write. + #[inline(always)] + unsafe fn write(&mut self, offset: u8, value: u8) { + assert_offset(offset); + let address_offset = offset + .checked_mul(u8::from(self.stride())) + .expect("offset * stride overflows u8; reduce stride"); + let addr = self.base().add_offset(address_offset); + // SAFETY: The caller ensured that the register address is safe to use. + unsafe { self._write_register(addr, value) } + } + + /* needs impl */ + + /// Returns the base [`RegisterAddress`]. + fn base(&self) -> Self::Address; + + /// Returns the configured stride. + /// + /// The stride is the fixed byte distance in physical address space between + /// consecutive logical registers, i.e. how much the address increases when + /// moving from one register index to the next. + fn stride(&self) -> NonZeroU8; + + /// PRIVATE API! Use [`Self::read`]! + /// + /// Reads one byte from the specified register. + /// + /// This needs a mutable reference as reads can have side effects on the + /// device, depending on the register. + /// + /// # Arguments + /// + /// - `address`: The total address of the register. + /// + /// # Safety + /// + /// Callers must ensure that the provided address is valid and safe to read. + #[doc(hidden)] + unsafe fn _read_register(&mut self, address: Self::Address) -> u8; + + /// PRIVATE API! Use [`Self::write`]! + /// + /// Writes one byte to the specified register. + /// + /// Writes can have side effects on the device, depending on the register. + /// + /// # Arguments + /// + /// - `address`: The total address of the register. + /// + /// # Safety + /// + /// Callers must ensure that the provided address is valid and safe to write. + #[doc(hidden)] + unsafe fn _write_register(&mut self, address: Self::Address, value: u8); +} 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) + ); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c24bf9f..0f29a3d 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 @@ -46,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}; @@ -73,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!"); //! ``` @@ -142,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}; @@ -167,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!"); /// ``` @@ -191,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!"); /// ``` @@ -217,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 /// @@ -244,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. /// @@ -951,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. ///