Skip to content

Commit c1578b3

Browse files
authored
Merge pull request #15 from remimimimi/master
Add support for memory mapped UARTs
2 parents 6a6c117 + cd62d85 commit c1578b3

File tree

5 files changed

+293
-106
lines changed

5 files changed

+293
-106
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ edition = "2018"
99

1010
[dependencies]
1111
bitflags = "1.1.0"
12+
13+
[target.'cfg(target_arch = "x86_64")'.dependencies]
1214
x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"] }
1315

1416
[features]

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
[![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/)
44

5-
Minimal support for uart_16550 serial I/O.
5+
Minimal support for uart_16550 serial and memory mapped I/O.
66

77
## Usage
88

9+
### With usual serial port
10+
911
```rust
1012
use uart_16550::SerialPort;
1113

@@ -21,6 +23,23 @@ serial_port.send(42);
2123
let data = serial_port.receive();
2224
```
2325

26+
### With memory mapped serial port
27+
28+
```rust
29+
use uart_16550::MmioSerialPort;
30+
31+
const SERIAL_PORT_BASE_ADDRESS: usize = 0x1000_0000;
32+
33+
let mut serial_port = unsafe { MmioSerialPort::new(SERIAL_PORT_BASE_ADDRESS) };
34+
serial_port.init();
35+
36+
// Now the serial port is ready to be used. To send a byte:
37+
serial_port.send(42);
38+
39+
// To receive a byte:
40+
let data = serial_port.receive();
41+
```
42+
2443
## License
2544

2645
Licensed under the MIT license ([LICENSE](LICENSE) or <http://opensource.org/licenses/MIT>).

src/lib.rs

Lines changed: 40 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
//! Minimal support for uart_16550 serial I/O.
22
//!
33
//! # Usage
4+
5+
#![cfg_attr(
6+
target_arch = "x86_64",
7+
doc = "
8+
## With usual serial port
9+
```no_run
10+
use uart_16550::SerialPort;
11+
12+
const SERIAL_IO_PORT: u16 = 0x3F8;
13+
14+
let mut serial_port = unsafe { SerialPort::new(SERIAL_IO_PORT) };
15+
serial_port.init();
16+
17+
// Now the serial port is ready to be used. To send a byte:
18+
serial_port.send(42);
19+
20+
// To receive a byte:
21+
let data = serial_port.receive();
22+
```
23+
"
24+
)]
25+
26+
//! ## With memory mapped serial port
427
//!
528
//! ```no_run
6-
//! use uart_16550::SerialPort;
29+
//! use uart_16550::MmioSerialPort;
730
//!
8-
//! const SERIAL_IO_PORT: u16 = 0x3F8;
31+
//! const SERIAL_PORT_BASE_ADDRESS: usize = 0x1000_0000;
932
//!
10-
//! let mut serial_port = unsafe { SerialPort::new(SERIAL_IO_PORT) };
33+
//! let mut serial_port = unsafe { MmioSerialPort::new(SERIAL_PORT_BASE_ADDRESS) };
1134
//! serial_port.init();
1235
//!
1336
//! // Now the serial port is ready to be used. To send a byte:
@@ -16,13 +39,14 @@
1639
//! // To receive a byte:
1740
//! let data = serial_port.receive();
1841
//! ```
19-
2042
#![no_std]
2143
#![warn(missing_docs)]
44+
#![cfg_attr(feature = "nightly", feature(const_ptr_offset))]
45+
46+
#[cfg(not(any(feature = "stable", feature = "nightly")))]
47+
compile_error!("Either the `stable` or `nightly` feature must be enabled");
2248

2349
use bitflags::bitflags;
24-
use core::fmt;
25-
use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly};
2650

2751
macro_rules! wait_for {
2852
($cond:expr) => {
@@ -32,6 +56,16 @@ macro_rules! wait_for {
3256
};
3357
}
3458

59+
/// Memory mapped implementation
60+
pub mod mmio;
61+
#[cfg(target_arch = "x86_64")]
62+
/// Port asm commands implementation
63+
pub mod x86_64;
64+
65+
pub use crate::mmio::MmioSerialPort;
66+
#[cfg(target_arch = "x86_64")]
67+
pub use crate::x86_64::SerialPort;
68+
3569
bitflags! {
3670
/// Interrupt enable flags
3771
struct IntEnFlags: u8 {
@@ -52,102 +86,3 @@ bitflags! {
5286
// 6 and 7 unknown
5387
}
5488
}
55-
56-
/// An interface to a serial port that allows sending out individual bytes.
57-
pub struct SerialPort {
58-
data: Port<u8>,
59-
int_en: PortWriteOnly<u8>,
60-
fifo_ctrl: PortWriteOnly<u8>,
61-
line_ctrl: PortWriteOnly<u8>,
62-
modem_ctrl: PortWriteOnly<u8>,
63-
line_sts: PortReadOnly<u8>,
64-
}
65-
66-
impl SerialPort {
67-
/// Creates a new serial port interface on the given I/O port.
68-
///
69-
/// This function is unsafe because the caller must ensure that the given base address
70-
/// really points to a serial port device.
71-
pub const unsafe fn new(base: u16) -> SerialPort {
72-
SerialPort {
73-
data: Port::new(base),
74-
int_en: PortWriteOnly::new(base + 1),
75-
fifo_ctrl: PortWriteOnly::new(base + 2),
76-
line_ctrl: PortWriteOnly::new(base + 3),
77-
modem_ctrl: PortWriteOnly::new(base + 4),
78-
line_sts: PortReadOnly::new(base + 5),
79-
}
80-
}
81-
82-
/// Initializes the serial port.
83-
///
84-
/// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
85-
pub fn init(&mut self) {
86-
unsafe {
87-
// Disable interrupts
88-
self.int_en.write(0x00);
89-
90-
// Enable DLAB
91-
self.line_ctrl.write(0x80);
92-
93-
// Set maximum speed to 38400 bps by configuring DLL and DLM
94-
self.data.write(0x03);
95-
self.int_en.write(0x00);
96-
97-
// Disable DLAB and set data word length to 8 bits
98-
self.line_ctrl.write(0x03);
99-
100-
// Enable FIFO, clear TX/RX queues and
101-
// set interrupt watermark at 14 bytes
102-
self.fifo_ctrl.write(0xC7);
103-
104-
// Mark data terminal ready, signal request to send
105-
// and enable auxilliary output #2 (used as interrupt line for CPU)
106-
self.modem_ctrl.write(0x0B);
107-
108-
// Enable interrupts
109-
self.int_en.write(0x01);
110-
}
111-
}
112-
113-
fn line_sts(&mut self) -> LineStsFlags {
114-
unsafe { LineStsFlags::from_bits_truncate(self.line_sts.read()) }
115-
}
116-
117-
/// Sends a byte on the serial port.
118-
pub fn send(&mut self, data: u8) {
119-
unsafe {
120-
match data {
121-
8 | 0x7F => {
122-
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
123-
self.data.write(8);
124-
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
125-
self.data.write(b' ');
126-
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
127-
self.data.write(8)
128-
}
129-
_ => {
130-
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
131-
self.data.write(data);
132-
}
133-
}
134-
}
135-
}
136-
137-
/// Receives a byte on the serial port.
138-
pub fn receive(&mut self) -> u8 {
139-
unsafe {
140-
wait_for!(self.line_sts().contains(LineStsFlags::INPUT_FULL));
141-
self.data.read()
142-
}
143-
}
144-
}
145-
146-
impl fmt::Write for SerialPort {
147-
fn write_str(&mut self, s: &str) -> fmt::Result {
148-
for byte in s.bytes() {
149-
self.send(byte);
150-
}
151-
Ok(())
152-
}
153-
}

src/mmio.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use core::{
2+
fmt,
3+
sync::atomic::{AtomicPtr, Ordering},
4+
};
5+
6+
use crate::LineStsFlags;
7+
8+
/// An interface to a serial port that allows sending out individual bytes.
9+
pub struct MmioSerialPort {
10+
data: AtomicPtr<u8>,
11+
int_en: AtomicPtr<u8>,
12+
fifo_ctrl: AtomicPtr<u8>,
13+
line_ctrl: AtomicPtr<u8>,
14+
modem_ctrl: AtomicPtr<u8>,
15+
line_sts: AtomicPtr<u8>,
16+
}
17+
18+
impl MmioSerialPort {
19+
/// Creates a new serial port interface on the given memory mapped address.
20+
///
21+
/// This function is unsafe because the caller must ensure that the given base address
22+
/// really points to a serial port device.
23+
#[cfg(feature = "nightly")]
24+
pub const unsafe fn new(base: usize) -> Self {
25+
let base_pointer = base as *mut u8;
26+
Self {
27+
data: AtomicPtr::new(base_pointer),
28+
int_en: AtomicPtr::new(base_pointer.add(1)),
29+
fifo_ctrl: AtomicPtr::new(base_pointer.add(2)),
30+
line_ctrl: AtomicPtr::new(base_pointer.add(3)),
31+
modem_ctrl: AtomicPtr::new(base_pointer.add(4)),
32+
line_sts: AtomicPtr::new(base_pointer.add(5)),
33+
}
34+
}
35+
36+
#[cfg(feature = "stable")]
37+
pub unsafe fn new(base: usize) -> Self {
38+
let base_pointer = base as *mut u8;
39+
Self {
40+
data: AtomicPtr::new(base_pointer),
41+
int_en: AtomicPtr::new(base_pointer.add(1)),
42+
fifo_ctrl: AtomicPtr::new(base_pointer.add(2)),
43+
line_ctrl: AtomicPtr::new(base_pointer.add(3)),
44+
modem_ctrl: AtomicPtr::new(base_pointer.add(4)),
45+
line_sts: AtomicPtr::new(base_pointer.add(5)),
46+
}
47+
}
48+
49+
/// Initializes the serial port.
50+
///
51+
/// The default configuration of [38400/8-N-1](https://en.wikipedia.org/wiki/8-N-1) is used.
52+
pub fn init(&mut self) {
53+
let self_int_en = self.int_en.load(Ordering::Relaxed);
54+
let self_line_ctrl = self.line_ctrl.load(Ordering::Relaxed);
55+
let self_data = self.data.load(Ordering::Relaxed);
56+
let self_fifo_ctrl = self.fifo_ctrl.load(Ordering::Relaxed);
57+
let self_modem_ctrl = self.modem_ctrl.load(Ordering::Relaxed);
58+
unsafe {
59+
// Disable interrupts
60+
self_int_en.write(0x00);
61+
62+
// Enable DLAB
63+
self_line_ctrl.write(0x80);
64+
65+
// Set maximum speed to 38400 bps by configuring DLL and DLM
66+
self_data.write(0x03);
67+
self_int_en.write(0x00);
68+
69+
// Disable DLAB and set data word length to 8 bits
70+
self_line_ctrl.write(0x03);
71+
72+
// Enable FIFO, clear TX/RX queues and
73+
// set interrupt watermark at 14 bytes
74+
self_fifo_ctrl.write(0xC7);
75+
76+
// Mark data terminal ready, signal request to send
77+
// and enable auxilliary output #2 (used as interrupt line for CPU)
78+
self_modem_ctrl.write(0x0B);
79+
80+
// Enable interrupts
81+
self_int_en.write(0x01);
82+
}
83+
}
84+
85+
fn line_sts(&mut self) -> LineStsFlags {
86+
unsafe { LineStsFlags::from_bits_truncate(*self.line_sts.load(Ordering::Relaxed)) }
87+
}
88+
89+
/// Sends a byte on the serial port.
90+
pub fn send(&mut self, data: u8) {
91+
let self_data = self.data.load(Ordering::Relaxed);
92+
unsafe {
93+
match data {
94+
8 | 0x7F => {
95+
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
96+
self_data.write(8);
97+
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
98+
self_data.write(b' ');
99+
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
100+
self_data.write(8)
101+
}
102+
_ => {
103+
wait_for!(self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY));
104+
self_data.write(data);
105+
}
106+
}
107+
}
108+
}
109+
110+
/// Receives a byte on the serial port.
111+
pub fn receive(&mut self) -> u8 {
112+
let self_data = self.data.load(Ordering::Relaxed);
113+
unsafe {
114+
wait_for!(self.line_sts().contains(LineStsFlags::INPUT_FULL));
115+
self_data.read()
116+
}
117+
}
118+
}
119+
120+
impl fmt::Write for MmioSerialPort {
121+
fn write_str(&mut self, s: &str) -> fmt::Result {
122+
for byte in s.bytes() {
123+
self.send(byte);
124+
}
125+
Ok(())
126+
}
127+
}

0 commit comments

Comments
 (0)