UART
The Universional Asynchronous Receiver-Transmitter (UART for short) is an asychronous serial communication device found in most computers, often including even embedded or IoT devices. It is a ubiquoutous model/specification to allow communication between two devices through a serial port.
This crate aims for full implementation support of features from the 16550 UART (detailed specification here).
Usage
Below is an example of a core::fmt::Write
-implementing type that initializes, tests, and is able to write to a UART.
note: found at uart::writer::UartWriter
, enabled by default feature "writer"
.
use crate::{
Baud, Data, FifoControl, InterruptEnable, LineControl, LineStatus, ModemControl, Uart,
UartAddress,
};
pub struct UartWriter<A: UartAddress>(Uart<A, Data>);
impl<A: UartAddress> UartWriter<A> {
pub unsafe fn new(address: A) -> Option<Self> {
let mut uart = unsafe { Uart::<A, Data>::new(address) };
uart.write_line_control(LineControl::empty());
uart.write_fifo_control(FifoControl::empty());
uart.write_interrupt_enable(InterruptEnable::empty());
let mut uart = uart.into_dlab_mode();
uart.set_baud(Baud::B115200);
let mut uart = uart.into_data_mode();
uart.write_line_control(LineControl::BITS_8);
uart.write_modem_control(
ModemControl::REQUEST_TO_SEND
| ModemControl::OUT_1
| ModemControl::OUT_2
| ModemControl::LOOPBACK_MODE,
);
uart.write_byte(0x1F);
if uart.read_byte() != 0x1F {
return None;
}
uart.write_modem_control(
ModemControl::TERMINAL_READY | ModemControl::REQUEST_TO_SEND | ModemControl::OUT_1,
);
Some(Self(uart))
}
}
impl<A: UartAddress> core::fmt::Write for UartWriter<A> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
s.chars().try_for_each(|c| self.write_char(c))
}
fn write_char(&mut self, c: char) -> core::fmt::Result {
while !self.0.read_line_status().contains(LineStatus::THR_EMPTY) {
core::hint::spin_loop();
}
self.0.write_byte({
if c.is_ascii() { c as u8 } else { b'?' }
});
Ok(())
}
}
Notably, the above implementation is I/O-agnostic, and uses the crate features to accept both port-mapped and memory-mapped I/O. Below are examples within the crate of implementations that address both types of I/O.
Port-mapped I/O
note: found at uart::address::PortAddress
, enabled by default feature "address"
.
use crate::{ReadableRegister, RegisterAddress, UartAddress, WriteableRegister};
pub struct PortAddress(NonZero<u16>);
impl PortAddress {
pub const unsafe fn new(base: NonZero<u16>) -> Self {
Self(base)
}
}
unsafe impl UartAddress for PortAddress {
unsafe fn read(&self, register: ReadableRegister) -> u8 {
let port_address = self.0.checked_add(register.as_index()).unwrap();
let value: u8;
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!(
"in al, dx",
out("al") value,
in("dx") port_address.get(),
options(nostack, nomem, preserves_flags)
);
}
#[cfg(not(target_arch = "x86_64"))]
unimplemented!();
value
}
unsafe fn write(&self, register: WriteableRegister, value: u8) {
let port_address = self.0.checked_add(register.as_index()).unwrap();
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!(
"out dx, al",
in("dx") port_address.get(),
in("al") value,
options(nostack, nomem, preserves_flags)
);
}
#[cfg(not(target_arch = "x86_64"))]
unimplemented!();
}
}
Memory-mapped I/O
note: found at uart::address::MmioAddress
, enabled by default feature "address"
.
use crate::{ReadableRegister, RegisterAddress, UartAddress, WriteableRegister};
pub struct MmioAddress {
base: NonNull<u8>,
stride: usize,
}
impl MmioAddress {
pub const unsafe fn new(base: NonNull<u8>, stride: usize) -> Self {
Self { base, stride }
}
}
unsafe impl UartAddress for MmioAddress {
unsafe fn write(&self, register: WriteableRegister, value: u8) {
unsafe {
self.base
.byte_add(usize::from(register.as_index()) * self.stride)
.write_volatile(value);
}
}
unsafe fn read(&self, register: ReadableRegister) -> u8 {
unsafe {
self.base
.byte_add(usize::from(register.as_index()) * self.stride)
.read_volatile()
}
}
}