Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for memory mapped UARTs #15

Merged
merged 15 commits into from
Jun 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ edition = "2018"

[dependencies]
bitflags = "1.1.0"

[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"] }

[features]
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

[![Build Status](https://github.com/rust-osdev/uart_16550/workflows/Build/badge.svg)](https://github.com/rust-osdev/uart_16550/actions?query=workflow%3ABuild) [![Docs.rs Badge](https://docs.rs/uart_16550/badge.svg)](https://docs.rs/uart_16550/)

Minimal support for uart_16550 serial I/O.
Minimal support for uart_16550 serial and memory mapped I/O.

## Usage

### With usual serial port

```rust
use uart_16550::SerialPort;

Expand All @@ -21,6 +23,23 @@ serial_port.send(42);
let data = serial_port.receive();
```

### With memory mapped serial port

```rust
use uart_16550::MmioSerialPort;

const SERIAL_PORT_BASE_ADDRESS: usize = 0x1000_0000;

let mut serial_port = unsafe { MmioSerialPort::new(SERIAL_PORT_BASE_ADDRESS) };
serial_port.init();

// Now the serial port is ready to be used. To send a byte:
serial_port.send(42);

// To receive a byte:
let data = serial_port.receive();
```

## License

Licensed under the MIT license ([LICENSE](LICENSE) or <http://opensource.org/licenses/MIT>).
Expand Down
145 changes: 40 additions & 105 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
//! Minimal support for uart_16550 serial I/O.
//!
//! # Usage

#![cfg_attr(
target_arch = "x86_64",
doc = "
## With usual serial port
```no_run
use uart_16550::SerialPort;

const SERIAL_IO_PORT: u16 = 0x3F8;

let mut serial_port = unsafe { SerialPort::new(SERIAL_IO_PORT) };
serial_port.init();

// Now the serial port is ready to be used. To send a byte:
serial_port.send(42);

// To receive a byte:
let data = serial_port.receive();
```
"
)]

//! ## With memory mapped serial port
//!
//! ```no_run
//! use uart_16550::SerialPort;
//! use uart_16550::MmioSerialPort;
//!
//! const SERIAL_IO_PORT: u16 = 0x3F8;
//! const SERIAL_PORT_BASE_ADDRESS: usize = 0x1000_0000;
//!
//! let mut serial_port = unsafe { SerialPort::new(SERIAL_IO_PORT) };
//! let mut serial_port = unsafe { MmioSerialPort::new(SERIAL_PORT_BASE_ADDRESS) };
//! serial_port.init();
//!
//! // Now the serial port is ready to be used. To send a byte:
Expand All @@ -16,13 +39,14 @@
//! // To receive a byte:
//! let data = serial_port.receive();
//! ```

#![no_std]
#![warn(missing_docs)]
#![cfg_attr(feature = "nightly", feature(const_ptr_offset))]

#[cfg(not(any(feature = "stable", feature = "nightly")))]
compile_error!("Either the `stable` or `nightly` feature must be enabled");

use bitflags::bitflags;
use core::fmt;
use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly};

macro_rules! wait_for {
($cond:expr) => {
Expand All @@ -32,6 +56,16 @@ macro_rules! wait_for {
};
}

/// Memory mapped implementation
pub mod mmio;
#[cfg(target_arch = "x86_64")]
/// Port asm commands implementation
pub mod x86_64;

pub use crate::mmio::MmioSerialPort;
#[cfg(target_arch = "x86_64")]
pub use crate::x86_64::SerialPort;

bitflags! {
/// Interrupt enable flags
struct IntEnFlags: u8 {
Expand All @@ -52,102 +86,3 @@ bitflags! {
// 6 and 7 unknown
}
}

/// An interface to a serial port that allows sending out individual bytes.
pub struct SerialPort {
data: Port<u8>,
int_en: PortWriteOnly<u8>,
fifo_ctrl: PortWriteOnly<u8>,
line_ctrl: PortWriteOnly<u8>,
modem_ctrl: PortWriteOnly<u8>,
line_sts: PortReadOnly<u8>,
}

impl SerialPort {
/// Creates a new serial port interface on the given I/O port.
///
/// This function is unsafe because the caller must ensure that the given base address
/// really points to a serial port device.
pub const unsafe fn new(base: u16) -> SerialPort {
SerialPort {
data: Port::new(base),
int_en: PortWriteOnly::new(base + 1),
fifo_ctrl: PortWriteOnly::new(base + 2),
line_ctrl: PortWriteOnly::new(base + 3),
modem_ctrl: PortWriteOnly::new(base + 4),
line_sts: PortReadOnly::new(base + 5),
}
}

/// Initializes the serial port.
///
/// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
pub fn init(&mut self) {
unsafe {
// Disable interrupts
self.int_en.write(0x00);

// Enable DLAB
self.line_ctrl.write(0x80);

// Set maximum speed to 38400 bps by configuring DLL and DLM
self.data.write(0x03);
self.int_en.write(0x00);

// Disable DLAB and set data word length to 8 bits
self.line_ctrl.write(0x03);

// Enable FIFO, clear TX/RX queues and
// set interrupt watermark at 14 bytes
self.fifo_ctrl.write(0xC7);

// Mark data terminal ready, signal request to send
// and enable auxilliary output #2 (used as interrupt line for CPU)
self.modem_ctrl.write(0x0B);

// Enable interrupts
self.int_en.write(0x01);
}
}

fn line_sts(&mut self) -> LineStsFlags {
unsafe { LineStsFlags::from_bits_truncate(self.line_sts.read()) }
}

/// Sends a byte on the serial port.
pub fn send(&mut self, data: u8) {
unsafe {
match data {
8 | 0x7F => {
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self.data.write(8);
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self.data.write(b' ');
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self.data.write(8)
}
_ => {
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self.data.write(data);
}
}
}
}

/// Receives a byte on the serial port.
pub fn receive(&mut self) -> u8 {
unsafe {
wait_for!(self.line_sts().contains(LineStsFlags::INPUT_FULL));
self.data.read()
}
}
}

impl fmt::Write for SerialPort {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.send(byte);
}
Ok(())
}
}
127 changes: 127 additions & 0 deletions src/mmio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use core::{
fmt,
sync::atomic::{AtomicPtr, Ordering},
};

use crate::LineStsFlags;

/// An interface to a serial port that allows sending out individual bytes.
pub struct MmioSerialPort {
data: AtomicPtr<u8>,
int_en: AtomicPtr<u8>,
fifo_ctrl: AtomicPtr<u8>,
line_ctrl: AtomicPtr<u8>,
modem_ctrl: AtomicPtr<u8>,
line_sts: AtomicPtr<u8>,
}

impl MmioSerialPort {
/// Creates a new serial port interface on the given memory mapped address.
///
/// This function is unsafe because the caller must ensure that the given base address
/// really points to a serial port device.
#[cfg(feature = "nightly")]
pub const unsafe fn new(base: usize) -> Self {
let base_pointer = base as *mut u8;
Self {
data: AtomicPtr::new(base_pointer),
int_en: AtomicPtr::new(base_pointer.add(1)),
fifo_ctrl: AtomicPtr::new(base_pointer.add(2)),
line_ctrl: AtomicPtr::new(base_pointer.add(3)),
modem_ctrl: AtomicPtr::new(base_pointer.add(4)),
line_sts: AtomicPtr::new(base_pointer.add(5)),
}
}

#[cfg(feature = "stable")]
pub unsafe fn new(base: usize) -> Self {
let base_pointer = base as *mut u8;
Self {
data: AtomicPtr::new(base_pointer),
int_en: AtomicPtr::new(base_pointer.add(1)),
fifo_ctrl: AtomicPtr::new(base_pointer.add(2)),
line_ctrl: AtomicPtr::new(base_pointer.add(3)),
modem_ctrl: AtomicPtr::new(base_pointer.add(4)),
line_sts: AtomicPtr::new(base_pointer.add(5)),
}
}

/// Initializes the serial port.
///
/// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
pub fn init(&mut self) {
let self_int_en = self.int_en.load(Ordering::Relaxed);
let self_line_ctrl = self.line_ctrl.load(Ordering::Relaxed);
let self_data = self.data.load(Ordering::Relaxed);
let self_fifo_ctrl = self.fifo_ctrl.load(Ordering::Relaxed);
let self_modem_ctrl = self.modem_ctrl.load(Ordering::Relaxed);
unsafe {
// Disable interrupts
self_int_en.write(0x00);

// Enable DLAB
self_line_ctrl.write(0x80);

// Set maximum speed to 38400 bps by configuring DLL and DLM
self_data.write(0x03);
self_int_en.write(0x00);

// Disable DLAB and set data word length to 8 bits
self_line_ctrl.write(0x03);

// Enable FIFO, clear TX/RX queues and
// set interrupt watermark at 14 bytes
self_fifo_ctrl.write(0xC7);

// Mark data terminal ready, signal request to send
// and enable auxilliary output #2 (used as interrupt line for CPU)
self_modem_ctrl.write(0x0B);

// Enable interrupts
self_int_en.write(0x01);
}
}

fn line_sts(&mut self) -> LineStsFlags {
unsafe { LineStsFlags::from_bits_truncate(*self.line_sts.load(Ordering::Relaxed)) }
}

/// Sends a byte on the serial port.
pub fn send(&mut self, data: u8) {
let self_data = self.data.load(Ordering::Relaxed);
unsafe {
match data {
8 | 0x7F => {
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self_data.write(8);
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self_data.write(b' ');
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self_data.write(8)
}
_ => {
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
self_data.write(data);
}
}
}
}

/// Receives a byte on the serial port.
pub fn receive(&mut self) -> u8 {
let self_data = self.data.load(Ordering::Relaxed);
unsafe {
wait_for!(self.line_sts().contains(LineStsFlags::INPUT_FULL));
self_data.read()
}
}
}

impl fmt::Write for MmioSerialPort {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.send(byte);
}
Ok(())
}
}
Loading