2 releases
Uses old Rust 2015
0.1.1 | Mar 29, 2017 |
---|---|
0.1.0 | Mar 29, 2017 |
#15 in #volatile
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);