From f37d7afd4ac45db4c247375c0fde5d081b5f0687 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Thu, 13 May 2021 17:15:11 +0300 Subject: [PATCH 01/12] Update README.md --- README.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4a1942..a327fec 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 `port_{stable, nightly}` feature + ```rust use uart_16550::SerialPort; @@ -21,14 +23,33 @@ serial_port.send(42); let data = serial_port.receive(); ``` +### With `mmio_{stable, nightly}` feature + +```rust +use uart_16550::SerialPort; + +const SERIAL_IO_PORT: usize = 0x1000_0000; + +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(); +``` + ## License Licensed under the MIT license ([LICENSE](LICENSE) or ). ## Crate Feature Flags -* `nightly`: This is the default. -* `stable`: Use this to build with non-nightly rust. Needs `default-features = false`. +* `port_nightly`: This is the default. +* `port_stable`: Use this to build with non-nightly rust. Needs `default-features = false`. +* `mmio_nightly`: Use this to initialize serial port through memory mapped I/O. +* `mmio_stable`: Use this to build with non-nightly rust. Needs `default-features = false`. ## Building with stable rust From 5414ab07a4121ce166c1eff4bd6dd1b026341189 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Thu, 13 May 2021 17:15:34 +0300 Subject: [PATCH 02/12] Rename and add features --- Cargo.toml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5f76d6..ed4c503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,15 @@ edition = "2018" [dependencies] bitflags = "1.1.0" -x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"] } +x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"], optional = true } [features] -default = [ "nightly" ] -stable = [ "x86_64/external_asm" ] -nightly = [ "x86_64/inline_asm" ] +default = [ "port_nightly"] +#default = [ "port", "nightly" ] +port_stable = [ "x86_64/external_asm" ] +port_nightly = [ "x86_64/inline_asm" ] +mmio_stable = [] +mmio_nightly = [] [package.metadata.release] no-dev-version = true From fb207ba104ed491348d04acca3342111851b5570 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Thu, 13 May 2021 17:15:52 +0300 Subject: [PATCH 03/12] Add support for mmio init --- src/lib.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 161 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5fc793d..3bc0bd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ //! Minimal support for uart_16550 serial I/O. //! //! # Usage +//! ## With `port_{stable, nightly}` feature //! -//! ```no_run +//! ```rust //! use uart_16550::SerialPort; //! //! const SERIAL_IO_PORT: u16 = 0x3F8; @@ -16,14 +17,51 @@ //! // To receive a byte: //! let data = serial_port.receive(); //! ``` +//! +//! ## With `mmio_{stable, nightly}` feature +//! +//! ```rust +//! use uart_16550::SerialPort; +//! +//! const SERIAL_IO_PORT: usize = 0x1000_0000; +//! +//! 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(); +//! ``` #![no_std] #![warn(missing_docs)] +#![cfg_attr(feature = "mmio_nightly", feature(const_ptr_offset))] use bitflags::bitflags; use core::fmt; + +#[cfg(any( + all( + not(any(feature = "port_stable", feature = "port_nightly")), + not(any(feature = "mmio_stable", feature = "mmio_nightly")) + ), + all( + any(feature = "port_stable", feature = "port_nightly"), + any(feature = "mmio_stable", feature = "mmio_nightly") + ) +))] +compile_error!( + "One of these features must be enabled: `port_{stable, nightly}`, `mmio_{stable, nightly}`" +); + +#[cfg(any(feature = "port_stable", feature = "port_nightly"))] use x86_64::instructions::port::Port; +#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] +use core::sync::atomic::{AtomicPtr, Ordering}; + macro_rules! wait_for { ($cond:expr) => { while !$cond { @@ -53,6 +91,7 @@ bitflags! { } } +#[cfg(any(feature = "port_stable", feature = "port_nightly"))] /// An interface to a serial port that allows sending out individual bytes. pub struct SerialPort { data: Port, @@ -63,14 +102,15 @@ pub struct SerialPort { line_sts: Port, } +#[cfg(any(feature = "port_stable", feature = "port_nightly"))] 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. - #[cfg(feature = "nightly")] - pub const unsafe fn new(base: u16) -> SerialPort { - SerialPort { + #[cfg(feature = "port_nightly")] + pub const unsafe fn new(base: u16) -> Self { + Self { data: Port::new(base), int_en: Port::new(base + 1), fifo_ctrl: Port::new(base + 2), @@ -84,9 +124,9 @@ impl SerialPort { /// /// This function is unsafe because the caller must ensure that the given base address /// really points to a serial port device. - #[cfg(not(feature = "nightly"))] - pub unsafe fn new(base: u16) -> SerialPort { - SerialPort { + #[cfg(feature = "port_stable")] + pub unsafe fn new(base: u16) -> Self { + Self { data: Port::new(base), int_en: Port::new(base + 1), fifo_ctrl: Port::new(base + 2), @@ -160,6 +200,120 @@ impl SerialPort { } } +/// An interface to a serial port that allows sending out individual bytes. +#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] +pub struct SerialPort { + data: AtomicPtr, + int_en: AtomicPtr, + fifo_ctrl: AtomicPtr, + line_ctrl: AtomicPtr, + modem_ctrl: AtomicPtr, + line_sts: AtomicPtr, +} + +#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] +impl SerialPort { + /// 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 = "mmio_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 = "mmio_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 SerialPort { fn write_str(&mut self, s: &str) -> fmt::Result { for byte in s.bytes() { From 0454c413819b589cbb27c1f6df212b1c9e3b86a2 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Fri, 14 May 2021 22:41:51 +0300 Subject: [PATCH 04/12] Rename memory mapped SerialPort to MmioSerialPort --- src/lib.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3bc0bd0..3de2fa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! ## With `mmio_{stable, nightly}` feature //! //! ```rust -//! use uart_16550::SerialPort; +//! use uart_16550::MmioSerialPort; //! //! const SERIAL_IO_PORT: usize = 0x1000_0000; //! @@ -39,9 +39,10 @@ #![warn(missing_docs)] #![cfg_attr(feature = "mmio_nightly", feature(const_ptr_offset))] -use bitflags::bitflags; use core::fmt; +use bitflags::bitflags; + #[cfg(any( all( not(any(feature = "port_stable", feature = "port_nightly")), @@ -52,16 +53,17 @@ use core::fmt; any(feature = "mmio_stable", feature = "mmio_nightly") ) ))] -compile_error!( - "One of these features must be enabled: `port_{stable, nightly}`, `mmio_{stable, nightly}`" -); +compile_error!("One of these features must be enabled: `port_{stable, nightly}`, `mmio_{stable, nightly}`"); + +#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] +use core::sync::atomic::{ + AtomicPtr, + Ordering, +}; #[cfg(any(feature = "port_stable", feature = "port_nightly"))] use x86_64::instructions::port::Port; -#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] -use core::sync::atomic::{AtomicPtr, Ordering}; - macro_rules! wait_for { ($cond:expr) => { while !$cond { @@ -182,11 +184,11 @@ impl SerialPort { 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); - } + }, } } } @@ -202,7 +204,7 @@ impl SerialPort { /// An interface to a serial port that allows sending out individual bytes. #[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] -pub struct SerialPort { +pub struct MmioSerialPort { data: AtomicPtr, int_en: AtomicPtr, fifo_ctrl: AtomicPtr, @@ -295,11 +297,11 @@ impl SerialPort { 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); - } + }, } } } From 11acb5b6ba8caafb1a7f19aaeb1c073c903fd0b7 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Fri, 14 May 2021 23:11:22 +0300 Subject: [PATCH 05/12] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a327fec..13c9fef 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ let data = serial_port.receive(); ### With `mmio_{stable, nightly}` feature ```rust -use uart_16550::SerialPort; +use uart_16550::MmioSerialPort; -const SERIAL_IO_PORT: usize = 0x1000_0000; +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: From 42b7c0da2c60517b6d1486d5cf7852195b639347 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 16 May 2021 16:07:31 +0200 Subject: [PATCH 06/12] Simplify cargo features by making `x86_64` a target-specific dependency This enables the `MmioSerialPort` type unconditionally on all architectures. The `SerialPort` type is only enabled on `x86_64`. --- Cargo.toml | 13 ++- src/lib.rs | 266 +++----------------------------------------------- src/mmio.rs | 126 ++++++++++++++++++++++++ src/x86_64.rs | 119 ++++++++++++++++++++++ 4 files changed, 262 insertions(+), 262 deletions(-) create mode 100644 src/mmio.rs create mode 100644 src/x86_64.rs diff --git a/Cargo.toml b/Cargo.toml index ed4c503..73ce5fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,14 @@ edition = "2018" [dependencies] bitflags = "1.1.0" -x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"], optional = true } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"] } [features] -default = [ "port_nightly"] -#default = [ "port", "nightly" ] -port_stable = [ "x86_64/external_asm" ] -port_nightly = [ "x86_64/inline_asm" ] -mmio_stable = [] -mmio_nightly = [] +default = [ "nightly"] +stable = [ "x86_64/external_asm" ] +nightly = [ "x86_64/inline_asm" ] [package.metadata.release] no-dev-version = true diff --git a/src/lib.rs b/src/lib.rs index 3de2fa0..e707156 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,32 +37,12 @@ #![no_std] #![warn(missing_docs)] -#![cfg_attr(feature = "mmio_nightly", feature(const_ptr_offset))] - -use core::fmt; +#![cfg_attr(feature = "nightly", feature(const_ptr_offset))] use bitflags::bitflags; -#[cfg(any( - all( - not(any(feature = "port_stable", feature = "port_nightly")), - not(any(feature = "mmio_stable", feature = "mmio_nightly")) - ), - all( - any(feature = "port_stable", feature = "port_nightly"), - any(feature = "mmio_stable", feature = "mmio_nightly") - ) -))] -compile_error!("One of these features must be enabled: `port_{stable, nightly}`, `mmio_{stable, nightly}`"); - -#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] -use core::sync::atomic::{ - AtomicPtr, - Ordering, -}; - -#[cfg(any(feature = "port_stable", feature = "port_nightly"))] -use x86_64::instructions::port::Port; +#[cfg(not(any(feature = "stable", feature = "nightly")))] +compile_error!("Either the `stable` or `nightly` feature must be enabled"); macro_rules! wait_for { ($cond:expr) => { @@ -72,6 +52,14 @@ macro_rules! wait_for { }; } +pub mod mmio; +#[cfg(target_arch = "x86_64")] +pub mod x86_64; + +#[cfg(target_arch = "x86_64")] +pub use crate::x86_64::SerialPort; +pub use crate::mmio::MmioSerialPort; + bitflags! { /// Interrupt enable flags struct IntEnFlags: u8 { @@ -92,235 +80,3 @@ bitflags! { // 6 and 7 unknown } } - -#[cfg(any(feature = "port_stable", feature = "port_nightly"))] -/// An interface to a serial port that allows sending out individual bytes. -pub struct SerialPort { - data: Port, - int_en: Port, - fifo_ctrl: Port, - line_ctrl: Port, - modem_ctrl: Port, - line_sts: Port, -} - -#[cfg(any(feature = "port_stable", feature = "port_nightly"))] -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. - #[cfg(feature = "port_nightly")] - pub const unsafe fn new(base: u16) -> Self { - Self { - data: Port::new(base), - int_en: Port::new(base + 1), - fifo_ctrl: Port::new(base + 2), - line_ctrl: Port::new(base + 3), - modem_ctrl: Port::new(base + 4), - line_sts: Port::new(base + 5), - } - } - - /// 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. - #[cfg(feature = "port_stable")] - pub unsafe fn new(base: u16) -> Self { - Self { - data: Port::new(base), - int_en: Port::new(base + 1), - fifo_ctrl: Port::new(base + 2), - line_ctrl: Port::new(base + 3), - modem_ctrl: Port::new(base + 4), - line_sts: Port::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() - } - } -} - -/// An interface to a serial port that allows sending out individual bytes. -#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] -pub struct MmioSerialPort { - data: AtomicPtr, - int_en: AtomicPtr, - fifo_ctrl: AtomicPtr, - line_ctrl: AtomicPtr, - modem_ctrl: AtomicPtr, - line_sts: AtomicPtr, -} - -#[cfg(any(feature = "mmio_stable", feature = "mmio_nightly"))] -impl SerialPort { - /// 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 = "mmio_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 = "mmio_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 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..88f58d3 --- /dev/null +++ b/src/mmio.rs @@ -0,0 +1,126 @@ +use crate::LineStsFlags; +use core::{fmt, sync::atomic::{ + AtomicPtr, + Ordering, +}}; + +/// 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..6845f0f --- /dev/null +++ b/src/x86_64.rs @@ -0,0 +1,119 @@ +use x86_64::instructions::port::Port; +use crate::LineStsFlags; +use core::fmt; + +/// An interface to a serial port that allows sending out individual bytes. +pub struct SerialPort { + data: Port, + int_en: Port, + fifo_ctrl: Port, + line_ctrl: Port, + modem_ctrl: Port, + line_sts: Port, +} + +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. + #[cfg(feature = "nightly")] + pub const unsafe fn new(base: u16) -> Self { + Self { + data: Port::new(base), + int_en: Port::new(base + 1), + fifo_ctrl: Port::new(base + 2), + line_ctrl: Port::new(base + 3), + modem_ctrl: Port::new(base + 4), + line_sts: Port::new(base + 5), + } + } + + /// 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. + #[cfg(feature = "stable")] + pub unsafe fn new(base: u16) -> Self { + Self { + data: Port::new(base), + int_en: Port::new(base + 1), + fifo_ctrl: Port::new(base + 2), + line_ctrl: Port::new(base + 3), + modem_ctrl: Port::new(base + 4), + line_sts: Port::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(()) + } +} From f57977425b83a26941418d653a0c58752402cd65 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Fri, 21 May 2021 15:51:51 +0300 Subject: [PATCH 07/12] Add missing docs --- src/lib.rs | 4 +++- src/mmio.rs | 12 ++++++++---- src/x86_64.rs | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e707156..bba848e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,13 +52,15 @@ 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; -pub use crate::mmio::MmioSerialPort; bitflags! { /// Interrupt enable flags diff --git a/src/mmio.rs b/src/mmio.rs index 88f58d3..cc00c8f 100644 --- a/src/mmio.rs +++ b/src/mmio.rs @@ -1,8 +1,12 @@ +use core::{ + fmt, + sync::atomic::{ + AtomicPtr, + Ordering, + }, +}; + use crate::LineStsFlags; -use core::{fmt, sync::atomic::{ - AtomicPtr, - Ordering, -}}; /// An interface to a serial port that allows sending out individual bytes. pub struct MmioSerialPort { diff --git a/src/x86_64.rs b/src/x86_64.rs index 6845f0f..528cd06 100644 --- a/src/x86_64.rs +++ b/src/x86_64.rs @@ -1,6 +1,8 @@ +use core::fmt; + use x86_64::instructions::port::Port; + use crate::LineStsFlags; -use core::fmt; /// An interface to a serial port that allows sending out individual bytes. pub struct SerialPort { From 81d61d9cae155cac775b229c33d42e461939bfa8 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Tue, 25 May 2021 00:40:42 +0300 Subject: [PATCH 08/12] Fix documentation --- README.md | 10 ++++------ src/lib.rs | 46 +++++++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 13c9fef..1c14c3f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Minimal support for uart_16550 serial and memory mapped I/O. ## Usage -### With `port_{stable, nightly}` feature +### With usual serial port ```rust use uart_16550::SerialPort; @@ -23,7 +23,7 @@ serial_port.send(42); let data = serial_port.receive(); ``` -### With `mmio_{stable, nightly}` feature +### With memory mapped serial port ```rust use uart_16550::MmioSerialPort; @@ -46,10 +46,8 @@ Licensed under the MIT license ([LICENSE](LICENSE) or Date: Tue, 25 May 2021 00:47:01 +0300 Subject: [PATCH 09/12] Fix merge conflict --- src/x86_64.rs | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/src/x86_64.rs b/src/x86_64.rs index 528cd06..7b0373a 100644 --- a/src/x86_64.rs +++ b/src/x86_64.rs @@ -1,17 +1,17 @@ use core::fmt; -use x86_64::instructions::port::Port; +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: Port, - fifo_ctrl: Port, - line_ctrl: Port, - modem_ctrl: Port, - line_sts: Port, + int_en: PortWriteOnly, + fifo_ctrl: PortWriteOnly, + line_ctrl: PortWriteOnly, + modem_ctrl: PortWriteOnly, + line_sts: PortReadOnly, } impl SerialPort { @@ -19,31 +19,14 @@ impl SerialPort { /// /// 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: u16) -> Self { Self { data: Port::new(base), - int_en: Port::new(base + 1), - fifo_ctrl: Port::new(base + 2), - line_ctrl: Port::new(base + 3), - modem_ctrl: Port::new(base + 4), - line_sts: Port::new(base + 5), - } - } - - /// 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. - #[cfg(feature = "stable")] - pub unsafe fn new(base: u16) -> Self { - Self { - data: Port::new(base), - int_en: Port::new(base + 1), - fifo_ctrl: Port::new(base + 2), - line_ctrl: Port::new(base + 3), - modem_ctrl: Port::new(base + 4), - line_sts: Port::new(base + 5), + 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), } } @@ -93,11 +76,11 @@ impl SerialPort { 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); - }, + } } } } From 40a5ee1fb6d9fbebdf14cb43639754280dfe49b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <33205215+remimimimi@users.noreply.github.com> Date: Tue, 25 May 2021 01:12:22 +0300 Subject: [PATCH 10/12] Format file --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f86e24b..b28d988 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ bitflags = "1.1.0" x86_64 = { version = "0.14.0", default-features = false, features = ["instructions"] } [features] -default = [ "nightly"] +default = [ "nightly" ] stable = [ "x86_64/external_asm" ] nightly = [ "x86_64/inline_asm" ] From 305da02277616d9710db3d7db311322ffde43a62 Mon Sep 17 00:00:00 2001 From: remimimimi Date: Thu, 27 May 2021 16:40:32 +0300 Subject: [PATCH 11/12] Fix doc tests --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 291d0f8..5fbb08a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ target_arch = "x86_64", doc = " ## With usual serial port -```rust +```no_run use uart_16550::SerialPort; const SERIAL_IO_PORT: u16 = 0x3F8; @@ -25,7 +25,7 @@ let data = serial_port.receive(); //! ## With memory mapped serial port //! -//! ```rust +//! ```no_run //! use uart_16550::MmioSerialPort; //! //! const SERIAL_PORT_BASE_ADDRESS: usize = 0x1000_0000; @@ -85,4 +85,4 @@ bitflags! { const OUTPUT_EMPTY = 1 << 5; // 6 and 7 unknown } -} \ No newline at end of file +} From cd62d85b699ec62ba86cb65a3d7cc030c4f2f7cf Mon Sep 17 00:00:00 2001 From: remimimimi Date: Thu, 27 May 2021 16:41:03 +0300 Subject: [PATCH 12/12] Format file --- src/mmio.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/mmio.rs b/src/mmio.rs index cc00c8f..c8a0fb7 100644 --- a/src/mmio.rs +++ b/src/mmio.rs @@ -1,9 +1,6 @@ use core::{ fmt, - sync::atomic::{ - AtomicPtr, - Ordering, - }, + sync::atomic::{AtomicPtr, Ordering}, }; use crate::LineStsFlags; @@ -101,11 +98,11 @@ impl MmioSerialPort { 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); - }, + } } } }