How to make an access volatile without std library?

const DATA_SEG_BMASK: u32 = 0x0000_00FF;

#[repr(C)]
struct UartMmapRegs{
   txdata: u32, /*  Transmit data */
   rxdata: u32, /* Receive data  */
   txctrl: u32, /* Tx control */
   rxctrl: u32, /* Rx Control */
   ie: u32, /* Interrupt enable */
   ip: u32, /* Interrupt pending */
   div: u32, /* Divisor */
}

pub fn receive (&self) -> u32 {
        unsafe {
            return ((*self.0).rxdata) & DATA_SEG_BMASK ;
        }
    }

The method is called as below:

        if !uart.rxfifo_empty() {
            rxbuf[rxidx] = uart.receive();
            rxidx = rxidx + 1;
        }

Objdump shows it don't fetch from the memory mapped register.

800006f0 <receive>:
800006f0:	1141                	addi	sp,sp,-16
800006f2:	c62a                	sw	a0,12(sp)
800006f4:	4108                	lw	a0,0(a0)
800006f6:	4148                	lw	a0,4(a0)
800006f8:	893d                	andi	a0,a0,15
800006fa:	0141                	addi	sp,sp,16
800006fc:	8082                	ret

In embedded-rust (without std library), how to make an access to memory mapped register volatile?

1 Like

Maybe std::ptr::read_volatile?

1 Like

The OP asked for a solution that is no_std - luckily, read_volatile is actually defined in core::ptr, and the version in std is just a re-export: read_volatile in core::ptr - Rust

There is also the volatile crate, which is a slightly nicer wrapper around core::ptr functions.

5 Likes

Ideally, you should never create references to volatile memory, and always use raw pointers. It's best to do something like this:

#[repr(C)]
struct UartMmapRegs {
   txdata: u32, /*  Transmit data */
   rxdata: u32, /* Receive data  */
   txctrl: u32, /* Tx control */
   rxctrl: u32, /* Rx Control */
   ie: u32, /* Interrupt enable */
   ip: u32, /* Interrupt pending */
   div: u32, /* Divisor */
}

pub struct UartMmapRegsPtr {
    ptr: *mut UartMmapRegs,
}

impl UartMmapRegsPtr {
    pub fn receive(&self) -> u32 {
        unsafe {
            let ptr = self.ptr;
            let field_ptr = core::ptr::addr_of!(ptr.rxdata);
            core::ptr::read_volatile(field_ptr) & DATA_SEG_BMASK
        }
    }
}

This uses a wrapper struct to avoid creating a &UartMmapRegs as &self when calling the method, and uses addr_of! to convert the struct ptr into a field ptr without an intermediate reference.

6 Likes

For this kind of problem, I use volatile-register. It transparently wraps a primitive type, makes access volatile and lets you define access permissions (read only, write only or read write).

Guessing a bit on what permissions you need, something like this:

use volatile_register::{RO, WO};
const DATA_SEG_BMASK: u32 = 0x0000_00FF;

#[repr(C)]
struct UartMmapRegs{
   txdata: WO<u32>, /*  Transmit data */
   rxdata: RO<u32>, /* Receive data  */
   txctrl: RO<u32>, /* Tx control */
   rxctrl: WO<u32>, /* Rx Control */
   ie: WO<u32>, /* Interrupt enable */
   ip: RO<u32>, /* Interrupt pending */
   div: WO<u32>, /* Divisor */
}
impl UartMmapRegs {
    fn receive (&self) -> u32 {
        return self.rxdata.read() & DATA_SEG_BMASK ;
    }
}

Regarding that crate, I would like to be clear that volatile_register is in the "works on the compiler today, but the language does not promise that this will continue to work" area, whereas the solution I posted with raw pointers is in the "if this doesn't work, then there's a bug in the compiler" area.

1 Like

Interesting, why is that? Can you be more specific?

Is it related to this issue?

It's because the mere existence of a reference makes promises to the compiler that don't necessarily hold for volatile memory. For example, it promises that its ok for the compiler to insert extra reads to the memory, which could have side effects on some types of volatile memory. My snippet avoids that by using raw pointers instead of references.

And yes, that issue is about this.

1 Like

I'm probably wrong, but hasn't this been (recently) solved in the compiler by do not mark interior mutable shared refs as dereferenceable #98017? I'd like to understand this because I'm using volatile-register (and vcell) extensively.

Excerpt of code from volatile-register and its dependency vcell
use core::cell::UnsafeCell;
use core::ptr;

/// from crate vcell
#[repr(transparent)]
pub struct VolatileCell<T> {
    value: UnsafeCell<T>,
}
impl<T> VolatileCell<T> {
    /// Creates a new `VolatileCell` containing the given value
    pub const fn new(value: T) -> Self {
        VolatileCell {
            value: UnsafeCell::new(value),
        }
    }

    /// Returns a copy of the contained value
    #[inline(always)]
    pub fn get(&self) -> T
    where
        T: Copy,
    {
        unsafe { ptr::read_volatile(self.value.get()) }
    }
}

/// from crate volatile-register
/// Read-Only register
pub struct RO<T>
where
    T: Copy,
{
    register: VolatileCell<T>,
}

impl<T> RO<T>
where
    T: Copy,
{
    /// Reads the value of the register
    #[inline(always)]
    pub fn read(&self) -> T {
        self.register.get()
    }
}

In volatile-register, the register is stored in an UnsafeCell and the read/write accesses go through raw pointers. A (direct) reference to the inner register is never created.

What is created is a &UnsafeCell. As far as I understand, before #98017 was merged the compiler was marking the pointer inside the UnsafeCell as dereferenceable, which let LLVM insert spurious read/write accesses as you write. But after that PR was merged (to solve Arc::drop has a (potentially) dangling shared ref #55005 among other things), that is no longer the case.

So volatile-register (and vcell) should now be expected to just work fine... Or am I missing something?

Right, this kind of thing is a valid argument for "works on the compiler today", but it cannot be used to argue that we're in the "if this doesn't work, then there's a bug in the compiler" area.

To make that kind of argument, we should look at what the compiler actually promises, rather than at how the promise is implemented. Here is the relevant documentation:

For both &T without UnsafeCell<_> and &mut T, you must also not deallocate the data until the reference expires. As a special exception, given an &T, any part of it that is inside an UnsafeCell<_> may be deallocated during the lifetime of the reference, after the last time the reference is used (dereferenced or reborrowed). Since you cannot deallocate a part of what a reference points to, this means the memory an &T points to can be deallocted only if every part of it (including padding) is inside an UnsafeCell.

However, whenever a &UnsafeCell<T> is constructed or dereferenced, it must still point to live memory and the compiler is allowed to insert spurious reads if it can prove that this memory has not yet been deallocated.

from UnsafeCell docs

The "special exception" in this snippet is the reason that &UnsafeCell can't be dereferenceable. However, if you read the last part of this paragraph, you will see that the compiler reserves the right to insert spurious reads even for &UnsafeCell.

1 Like