#volatile #pointers #memory #hardware #memory-mapped #raw-pointers

nightly no-std volatile-ptr

Implementation of volatile pointers for I/O device access

2 releases

Uses old Rust 2015

0.1.1 Mar 29, 2017
0.1.0 Mar 29, 2017

#12 in #volatile

GPL-3.0 license

22KB
377 lines

Volatile memory operations.

This library contains wrappers around raw pointers to perform volatile memory operations. This is mainly useful for memory mapped I/O. Writing to the peripheral memory addresses will normally be optimized out by the compiler. The Volatile type wraps a memory address to perform volatile operations in order to force the compiler to keep these memory accesses and stores.

The creation of a Volatile pointer is generally unsafe, but the actual operations that you can perform on it are considered safe by Rust's standards. This is because the actual memory operations are performed through the Deref and DerefMut traits, which are defined as safe methods. It is important to remember that a Volatile pointer is nearly identical to a primitive pointer, and so all dereferencing operations on one should be considered unsafe (even if not enforced by the compiler).

Examples

use volatile::Volatile;

const IO_ADDR: *const u32 = 0x4000_4400 as *const _;

unsafe {
    let mut io_ptr = Volatile::new(IO_ADDR);
    // Some bit that we need to set for an IO operation
    *io_ptr |= 0b1 << 5;
}

On some embedded devices you may want to do something like wait for a certain amount of time to pass measured by some amount of ticks.

// Some tick counter that may be updated by a hardware interrupt
static mut TICKS: usize = 0;

while unsafe { TICKS < 10 } {/* wait for ticks to change */}

Normally, the Rust compilier would optimize this kind of operation into just an infinite loop, since the value of TICKS can't change in a single threaded environment, but TICKS could be updated by some hardware interrupt, so we want to keep reloading the value in order to check it. So to get around this we can use a Volatile pointer to force the compiler to reload the value every time through the loop.

use volatile::Volatile;

static mut TICKS: usize = 0;

unsafe {
    let ticks_ptr = Volatile::new(&TICKS);
    while *ticks_ptr < 10 {/* wait for ticks to change */}
}

Now the value of TICKS will be reloaded every time through the loop.

Oftentimes when working with memory mapped peripherals, you will have a block of memory that you want to be working on that contains control, status, and data registers for some hardware peripheral. These are often best represented as structs with each of their registers as fields, but without volatile operations, loads and stores to these memory addresses often get optimized out by the compiler. To get around this you can use a Volatile pointer to point at the mapped address and have it be represented as a struct of the correct type.

use volatile::Volatile;

const USART_ADDR: *const Usart = 0x4000_4400 as *const _;
// For transmitting and receiving data over serial
#[repr(C)]
struct Usart {
    control_reg: u32,
    status_reg: u32,
    tx_data_reg: u32,
    rx_data_reg: u32,
}

let recieved = unsafe {
    let mut usart_block = Volatile::new(USART_ADDR);
    // Set some bits, these will be hardware specific
    usart_block.control_reg |= 0b11 << 5;

    while usart_block.status_reg & 0b1 << 7 == 0 {/*wait for hardware to set a bit*/}

    // Transmit some data
    usart_block.tx_data_reg = 100;

    while usart_block.status_reg & 0b1 << 6 == 0 {/*wait for hardware to set some other bit*/}

    // Receive some data
    usart_block.rx_data_reg
};

Every field access to a pointed at struct will be considered volatile and so will not be optimized out by the compiler.

Just as with primitive pointers, Volatile pointers can be created from valid references safely, though their use should still be considered unsafe.

use volatile::Volatile;

let x: u32 = 0;
let ptr = Volatile::from(&x);

No runtime deps