Skip to main content

Crate uart_16550

Crate uart_16550 

Source
Expand description

§uart_16550

Simple yet highly configurable low-level driver for 16550 UART devices, typically known and used as serial ports or COM ports. Easy integration into Rust while providing fine-grained control where needed (e.g., for kernel drivers).

The “serial device” or “COM port” in typical x86 machines is almost always backed by a 16550 UART devices, may it be physical or emulated. This crate offers convenient and powerful abstractions for these devices, and also works for other architectures, such as ARM or RISC-V, by offering support for MMIO-mapped devices.

Serial ports are especially useful for debugging or operating system learning projects. See Uart16550 to get started.

§Features

  • ✅ Full configure, transmit, receive, and interrupt support for UART 16550–compatible devices
  • ✅ High-level, ergonomic abstractions and types paired with support for plain integers
  • ✅ Very easy to integrate, highly configurable when needed
  • ✅ Validated on real hardware as well as across different virtual machines
  • ✅ Fully type-safe and derived directly from the official specification
  • ✅ Supports both x86 port-mapped I/O and memory-mapped I/O (MMIO)
  • no_std-compatible and allocation-free by design

§Focus, Scope & Limitations

While serial ports are often used in conjunction with VT102-like terminal emulation, the primary focus of this crate is strict specification compliance and convenient direct access to the underlying hardware for transmitting and receiving bytes, including all necessary device configuration.

For basic terminal-related functionality, such as newline normalization and backspace handling, we provide Uart16550Tty as a basic convenience layer.

§Overview

Use Uart16550Tty for a quick start. For more fine-grained low-level control, please have a look at Uart16550 instead.

§Example (Minimalistic)

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?");

See Uart16550Tty for more details.

§Example (More low-level control)

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).expect("should be valid port") };
//                                 ^ or `new_port(0x3f8)`
uart.init(Config::default()).expect("should init device successfully");
uart.test_loopback().expect("should have working loopback mode");
uart.check_connected().expect("should have physically connected receiver");
uart.send_bytes_exact(b"hello world!");

See Uart16550 for more details.

§Testing on Real Hardware

§Establish a Serial Connection and Test Using Linux

You need two machines, one must have a physical COM1 port. In this example, we’re using Linux.

Connect your COM1 port to another computer. You will need:

  • Cable: COM1 pin-out to DE9 (RS-232)
  • Cable: DE-9 (RS-232) to USB Serial
  • Null modem component (can be a cable or adapter): This enables point-to-point communication by crossing the RX and TX lines of both communication partners.

Test serial connection works:

  • Machine 1: $ sudo minicom -D /dev/ttyS0
  • Machine 2: $ sudo minicom -D /dev/ttyUSB0

Most likely, both Linux machines will use the default baud rate of BaudRate::Baud9600. If not, boot the Linux machines with an updated command line.

If you can send data between both parties, you can proceed with the next step.

§Test This Driver

Build your own (mini) operating system using this driver and check if you can receive data from Machine 2 (see above) or send data to it.

A relatively easy and flexible approach is to build a UEFI application and copy the resulting EFI file onto a USB stick with a bootable partition to path EFI\BOOT\BOOTX64.EFI.

The workflow with minicom on Machine 2 is the same.

Modules§

backend
Abstraction over the I/O backend (Hardware Abstraction Layer (HAL)).
spec
Constants, Register Offsets, and Register Bits.

Structs§

ByteReceiveError
There is currently no data to read.
Config
Configuration for a Uart16550.
ConfigRegisterDump
A dump of all (readable) config registers of Uart16550.
Uart16550
Powerful abstraction over a 16550 UART device with access to low-level details paired with strong flexibility for higher-level layers.
Uart16550Tty
Thin opinionated abstraction over Uart16550 that helps to send Rust strings easily to the other side, assuming the remote is a TTY (terminal).

Enums§

BaudRate
The speed of data transmission, measured in symbols (bits) per second.
ByteSendError
Errors that happen when trying to send a byte
InitError
Errors that can happen when a Uart16550 initialized in Uart16550::init.
InvalidAddressError
The specified address is invalid because it is either null or doesn’t allow for NUM_REGISTERS - 1 subsequent addresses.
LoopbackError
The loopback test failed.
RemoteReadyToReceiveError
Errors indicating the device is not ready to send data.
Uart16550TtyError
Errors that Uart16550Tty::new_port and Uart16550Tty::new_mmio may return.