Why doesn't Writeable::set take a mutable reference? #4471
-
|
I'm using |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 5 replies
-
|
There's a couple issues at play here, and others with a slightly firmer grasp than me on detailed nuances of Rust soundness (@lschuermann , @jrvanwhy) might be able to give some more context (and also may need to correct me; I'm still more years a low-level C programmer than a Rust guru..., though trying to answer things like this definitely help deepen my understanding of Rust...) First: The abstraction of For Tock more specifically, this has not been an issue in practice as all Tock platforms are single-core with a single execution stack [a minimal top-half interrupt handler just pends tasks; i.e., there is no concurrency] (though, there is also work to relax this restriction). Further, creation of references to hardware registers is restricted via I suspect part of the motivation for not requiring the mutable reference is that (1) Tock is old [the project predates Rust 1.0], and (2) Rust's capacity for reasoning about "partial mutable access", i.e., to one member of a struct was once non-existent and is still quite limited. While it's been reworked a few times, the Tock MMIO abstraction is obviously somewhat fundamental, and thus quite old as well. In particular, since the underlying hardware peripheral is represented as a monolithic struct*, requiring mutability makes current code patterns in Tock awkward from a quick attempt at seeing what happens with the change. At this point, I should acknowledge that "well, this is a problem with this library, it just isn't a problem the library maintainers happen to have" is probably a dissatisfying answer. As mentioned above, it is a problem coming down the pike for Tock, but I think fixing this is fairly invasive in practice, and is thus unlikely to change in the current Tock Registers. Rather, it is something that would be better addressed in the Tock Registers 2.0 rewrite (i.e. #4001). From a quick look, at the moment, #4001 does not touch this particular part of the registers interface, but perhaps it should. I know the 2.0 rewrite has stalled for a bit, but I believe @jrvanwhy is hopefully going to be able to pick it up again in earnest in the late-summer / early-fall timeframe. *A design decision which is really a holdover from the C/C++ idiom of trying to define a struct that matches the underlying hardware memory map. With a future, Rust-ier 'always deref individual pointers' approach, the monolithic struct-of-cells is no longer necessary. |
Beta Was this translation helpful? Give feedback.
-
|
To expand on both @ppannuto's and @alevy's answer, there's a couple parts to the
|
Beta Was this translation helpful? Give feedback.
-
This is a great question. In short, the reason why this is not an issue in practice is that the included You can, however, build your own custom types that are either owned (instead of designed to live behind references) and implement use std::cell::Cell;
use std::sync::{Mutex, RwLock};
use tock_registers::interfaces::Writeable;
use tock_registers::registers::WriteOnly;
fn main() {
// Pretend that a `Box<usize>` allocation is an MMIO register, and
// transmute the register pointer into a `WriteOnly` register:
let register_ptr: *mut usize = Box::leak(Box::new(0_usize)) as *mut usize;
let rw_reg: &WriteOnly<usize> = unsafe { std::mem::transmute(register_ptr) };
// We can write even though we only have a readable reference! This is fine
// though, as an `&WriteOnly` uses interior mutability:
let rwlocked_rw_reg = RwLock::new(rw_reg);
rwlocked_rw_reg.read().unwrap().set(1);
// However, this fails to compile. The `WriteOnly` type does not implement
// `Sync` to permit concurrent accesses, and so neither does `RwLock`:
std::thread::spawn(|| { // doesn't compile!!!
rwlocked_rw_reg.read().unwrap().set(2);
});
// Unfortunately, a Mutex doesn't help either, because you'd never have an
// _owned_ copy of your register. The only thing that a Mutex will add
// `Sync` to is the immutable reference stored within it, which itself
// points to a type that is not `Sync` and uses interior mutability:
let mutex_rw_reg = Mutex::new(rw_reg);
mutex_rw_reg.lock().unwrap().set(1);
std::thread::spawn(|| { // doesn't compile!!!
mutex_rw_reg.lock().unwrap().set(2);
});
// What does work is if we create our own, owned register abstraction and
// have it either internally synchronize accesses, ...
struct InternallySyncReg(Mutex<usize>);
impl Writeable for InternallySyncReg {
type T = usize;
type R = ();
fn set(&self, value: usize) {
*self.0.lock().unwrap() = value;
}
}
let int_sync_reg = InternallySyncReg(Mutex::new(0));
int_sync_reg.set(0);
std::thread::spawn(move || {
int_sync_reg.set(1);
});
// ... or use one that has internal mutability but is _owned_ (instead of
// being behind a shared reference), and wrap that in a Mutex (RwLock won't
// work, for the same reason as the first example):
struct NonSyncReg(Cell<usize>);
impl Writeable for NonSyncReg {
type T = usize;
type R = ();
fn set(&self, value: usize) {
self.0.set(value);
}
}
let mutex_non_sync_reg = Mutex::new(NonSyncReg(Cell::new(0)));
mutex_non_sync_reg.lock().unwrap().set(1);
std::thread::spawn(move || {
mutex_non_sync_reg.lock().unwrap().set(1);
});
} |
Beta Was this translation helpful? Give feedback.
-
|
Okay, I've done a lot of thinking and experimenting with UIO devices, their registers, mmapping them, and thread safety, and I'm now confident that I've made a safe and sound implementation! Thanks all for the detailed information! |
Beta Was this translation helpful? Give feedback.
-
|
Closing the discussion for now since this seems to be resolved. |
Beta Was this translation helpful? Give feedback.
There's a couple issues at play here, and others with a slightly firmer grasp than me on detailed nuances of Rust soundness (@lschuermann , @jrvanwhy) might be able to give some more context (and also may need to correct me; I'm still more years a low-level C programmer than a Rust guru..., though trying to answer things like this definitely help deepen my understanding of Rust...)
First: The abstraction of
VolatileCellholding a reference to memory is fundamentally unsound. We have WIP re-write fortock-registersthat addresses this, but it's a bigger project: #4001. This comment in that PR has some links to more context around volatile memory access issues in Rust more generally.For β¦