diff --git a/Cargo.toml b/Cargo.toml index 086bb8e..b28d988 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/README.md b/README.md index f4a1942..1c14c3f 100644 --- a/README.md +++ b/README.md @@ -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; @@ -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 ). diff --git a/src/lib.rs b/src/lib.rs index 5014634..5fbb08a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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: @@ -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) => { @@ -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 { @@ -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, - int_en: PortWriteOnly, - fifo_ctrl: PortWriteOnly, - line_ctrl: PortWriteOnly, - modem_ctrl: PortWriteOnly, - line_sts: PortReadOnly, -} - -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(()) - } -} diff --git a/src/mmio.rs b/src/mmio.rs new file mode 100644 index 0000000..c8a0fb7 --- /dev/null +++ b/src/mmio.rs @@ -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, + int_en: AtomicPtr, + fifo_ctrl: AtomicPtr, + line_ctrl: AtomicPtr, + modem_ctrl: AtomicPtr, + line_sts: AtomicPtr, +} + +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(()) + } +} diff --git a/src/x86_64.rs b/src/x86_64.rs new file mode 100644 index 0000000..7b0373a --- /dev/null +++ b/src/x86_64.rs @@ -0,0 +1,104 @@ +use core::fmt; + +use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; + +use crate::LineStsFlags; + +/// An interface to a serial port that allows sending out individual bytes. +pub struct SerialPort { + data: Port, + int_en: PortWriteOnly, + fifo_ctrl: PortWriteOnly, + line_ctrl: PortWriteOnly, + modem_ctrl: PortWriteOnly, + line_sts: PortReadOnly, +} + +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) -> Self { + Self { + 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(()) + } +}