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

Read write ports #66

Merged
merged 2 commits into from
Mar 22, 2019
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
90 changes: 76 additions & 14 deletions src/instructions/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,119 @@

use core::marker::PhantomData;

pub use crate::structures::port::PortReadWrite;
pub use crate::structures::port::{PortRead, PortWrite, PortReadWrite};

impl PortReadWrite for u8 {
impl PortRead for u8 {
#[inline]
unsafe fn read_from_port(port: u16) -> u8 {
let value: u8;
asm!("inb %dx, %al" : "={al}"(value) : "{dx}"(port) :: "volatile");
value
}

#[inline]
unsafe fn write_to_port(port: u16, value: u8) {
asm!("outb %al, %dx" :: "{dx}"(port), "{al}"(value) :: "volatile");
}
}

impl PortReadWrite for u16 {
impl PortRead for u16 {
#[inline]
unsafe fn read_from_port(port: u16) -> u16 {
let value: u16;
asm!("inw %dx, %ax" : "={ax}"(value) : "{dx}"(port) :: "volatile");
value
}

#[inline]
unsafe fn write_to_port(port: u16, value: u16) {
asm!("outw %ax, %dx" :: "{dx}"(port), "{ax}"(value) :: "volatile");
}
}

impl PortReadWrite for u32 {
impl PortRead for u32 {
#[inline]
unsafe fn read_from_port(port: u16) -> u32 {
let value: u32;
asm!("inl %dx, %eax" : "={eax}"(value) : "{dx}"(port) :: "volatile");
value
}
}

impl PortWrite for u8 {
#[inline]
unsafe fn write_to_port(port: u16, value: u8) {
asm!("outb %al, %dx" :: "{dx}"(port), "{al}"(value) :: "volatile");
}
}

impl PortWrite for u16 {
#[inline]
unsafe fn write_to_port(port: u16, value: u16) {
asm!("outw %ax, %dx" :: "{dx}"(port), "{ax}"(value) :: "volatile");
}
}

impl PortWrite for u32 {
#[inline]
unsafe fn write_to_port(port: u16, value: u32) {
asm!("outl %eax, %dx" :: "{dx}"(port), "{eax}"(value) :: "volatile");
}
}

impl PortReadWrite for u8 {}
impl PortReadWrite for u16 {}
impl PortReadWrite for u32 {}

/// A read only I/O port.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortReadOnly<T: PortRead> {
port: u16,
phantom: PhantomData<T>,
}

/// A write only I/O port.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortWriteOnly<T: PortWrite> {
port: u16,
phantom: PhantomData<T>,
}

/// An I/O port.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Port<T: PortReadWrite> {
port: u16,
phantom: PhantomData<T>,
}

impl<T: PortRead> PortReadOnly<T> {
/// Creates a read only I/O port with the given port number.
pub const fn new(port: u16) -> PortReadOnly<T> {
PortReadOnly {
port: port,
phantom: PhantomData,
}
}

/// Reads from the port.
///
/// This function is unsafe because the I/O port could have side effects that violate memory
/// safety.
#[inline]
pub unsafe fn read(&self) -> T {
T::read_from_port(self.port)
}
}

impl<T: PortWrite> PortWriteOnly<T> {
/// Creates a write only I/O port with the given port number.
pub const fn new(port: u16) -> PortWriteOnly<T> {
PortWriteOnly {
port: port,
phantom: PhantomData,
}
}

/// Writes to the port.
///
/// This function is unsafe because the I/O port could have side effects that violate memory
/// safety.
#[inline]
pub unsafe fn write(&mut self, value: T) {
T::write_to_port(self.port, value)
}
}

impl<T: PortReadWrite> Port<T> {
/// Creates an I/O port with the given port number.
pub const fn new(port: u16) -> Port<T> {
Expand Down
18 changes: 15 additions & 3 deletions src/structures/port.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
//! A trait for accessing I/O ports.
//! Traits for accessing I/O ports.

/// A helper trait that implements the actual port operations.
/// A helper trait that implements the read port operation.
///
/// On x86, I/O ports operate on either `u8` (via `inb`/`outb`), `u16` (via `inw`/`outw`),
/// or `u32` (via `inl`/`outl`). Therefore this trait is implemented for exactly these types.
pub trait PortReadWrite {
pub trait PortRead {
/// Reads a `Self` value from the given port.
///
/// This function is unsafe because the I/O port could have side effects that violate memory
/// safety.
unsafe fn read_from_port(port: u16) -> Self;
}

/// A helper trait that implements the write port operation.
///
/// On x86, I/O ports operate on either `u8` (via `inb`/`outb`), `u16` (via `inw`/`outw`),
/// or `u32` (via `inl`/`outl`). Therefore this trait is implemented for exactly these types.
pub trait PortWrite {
/// Writes a `Self` value to the given port.
///
/// This function is unsafe because the I/O port could have side effects that violate memory
/// safety.
unsafe fn write_to_port(port: u16, value: Self);
}

/// A helper trait that implements the read/write port operations.
///
/// On x86, I/O ports operate on either `u8` (via `inb`/`outb`), `u16` (via `inw`/`outw`),
/// or `u32` (via `inl`/`outl`). Therefore this trait is implemented for exactly these types.
pub trait PortReadWrite: PortRead + PortWrite {}
75 changes: 75 additions & 0 deletions testing/src/bin/test-port-read-write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#![feature(const_fn)]
#![no_std] // don't link the Rust standard library
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points

#[cfg(not(test))]
use core::panic::PanicInfo;
use testing::{exit_qemu, serial_println};
use x86_64::instructions::port::{PortReadOnly, PortWriteOnly, Port};

// This port tells the data port which index to read from
const CRT_INDEX_PORT: u16 = 0x3D4;

// This port stores the data for the index set by the index port
const CRT_DATA_PORT: u16 = 0x3D5;

// The offset crt register is used because it's a port with no reserved
// bits that won't crash the system when written to
const OFFSET_REGISTER: u8 = 0x0A;

// A randomly chosen value to test againts
const TEST_VALUE: u8 = 0b10101010;

/// This function is the entry point, since the linker looks for a function
/// named `_start_` by default.
#[cfg(not(test))]
#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
let mut crt_index_port = PortWriteOnly::<u8>::new(CRT_INDEX_PORT);
let mut crt_read_write_data_port = Port::<u8>::new(CRT_DATA_PORT);
let crt_data_read_only_port = PortReadOnly::<u8>::new(CRT_DATA_PORT);

unsafe {
// Set the offset register as the index using PortWriteOnly
crt_index_port.write(OFFSET_REGISTER);

// Write the test value to the data port using Port
crt_read_write_data_port.write(TEST_VALUE);

// Read the test value using PortReadOnly
let read_only_test_value = crt_data_read_only_port.read() & 0xFF;

// Read the test value using PortReadWrite
let read_write_test_value = crt_read_write_data_port.read() & 0xFF;

if read_only_test_value != TEST_VALUE {
panic!("PortReadOnly: {} does not match expected value {}", read_only_test_value, TEST_VALUE);
}

if read_write_test_value != TEST_VALUE {
panic!("PortReadWrite: {} does not match expected value {}", read_write_test_value, TEST_VALUE);
}
}

serial_println!("ok");

unsafe {
exit_qemu();
}

loop {}
}

/// This function is called on panic.
#[cfg(not(test))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
serial_println!("failed");

serial_println!("{}", info);

unsafe {
exit_qemu();
}
loop {}
}