0

In embedded programming, a memory-mapped peripheral can be accessed through a structure placed at the correct address. How do I ensure memory accesses to the peripheral are indeed performed on the bus in Rust? In C, this would be done by making the variable or fields volatile.

Consider this example:

#[repr(C)]
struct Periph {
    sr: u32, /* Status  */
    cr: u32, /* Control */
    dr: u32, /* Data    */
}

const PERIPH1: *mut Periph = 0x10001200 as *mut Periph;
const PERIPH2: *mut Periph = 0x10001400 as *mut Periph;

fn useperiph(p: &mut Periph) -> i32 {
    p.cr = CR_CONSTANT;
    if p.cr != CR_CONSTANT {
        /* Peripheral was not enabled */
        return -1;
    }

    /* Loop a few micro seconds until ready */
    while p.sr != SR_CONSTANT {}

    /* Write values to hardware FIFO */
    p.dr = DATA1;
    p.dr = DATA2;
    p.dr = DATA3;
    0
}

I need to ensure that the compiler won't remove the check on the control register, will leave all loads of the status register, and won't collapse the three stores to the data register. How do I do that?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
pj5634
  • 1
  • 1
  • That is a duplicate of https://stackoverflow.com/q/35009015/1233251, except that the answer is outdated. I think I should make an updated answer there instead of replicating information here. – E_net4 Jun 12 '17 at 23:49
  • Please see my answer [here](https://stackoverflow.com/a/44510388/1233251). – E_net4 Jun 13 '17 at 00:03

1 Answers1

0

The best I know how to do is to use something akin to the volatile_cell library from Zinc. The structure for the memory-mapped peripheral registers has VolatileCell<u32>-typed registers rather than plain u32. VolatileCell wraps a value, has the same size, and provides accessors which use core::ptr::read_volatile and core::ptr::write_volatile. Stripped down to its essentials, VolatileCell looks like this:

#[repr(C)]
pub struct VolatileCell<T> {
    value: T,
}

impl<T> VolatileCell<T> {

    /// Get register value.
    #[inline]
    pub fn get(&self) -> T {
        unsafe {
            read_volatile(&self.value)
        }
    }

    /// Set register value.
    #[inline]
    pub fn set(&self, value: T) {
        unsafe {
            write_volatile(&self.value as *const T as *mut T, value)
        }
    }
}

And then the peripheral register structure is written using VolatileCell<u32> fields:

#[repr(C)]
struct Periph {
    sr: VolatileCell<u32>, /* Status  */
    cr: VolatileCell<u32>, /* Control */
    dr: VolatileCell<u32>, /* Data    */
}

const PERIPH1: *mut Periph = 0x10001200 as *mut Periph;
const PERIPH2: *mut Periph = 0x10001400 as *mut Periph;

fn useperiph(p: &mut Periph) -> i32 {
    p.cr.set(CR_CONSTANT);
    if p.cr.get() != CR_CONSTANT {
        /* Peripheral was not enabled */
        return -1;
    }

    /* Loop a few micro seconds until ready */
    while p.sr.get() != SR_CONSTANT {}

    p.dr.set(DATA1);
    p.dr.set(DATA2);
    p.dr.set(DATA3);
    0
}

The resulting code has the correct semantics, and is only slightly more verbose than using u32 directly. Being explicit is arguably an upside.

pj5634
  • 1
  • 1