Add SyncUnsafeCell. by m-ou-se · Pull Request #95438 · rust-lang/rust (original) (raw)
(Edited to provide full context)
I've found myself needing something like SyncUnsafeCell
when working with hardware.
In the Atmel SAME70 microcontroller, the hardware uses DMA (direct memory access) in order to send and receive ethernet frames. In order to do that (messy PoC code here), the process looks (roughly) like this:
- The software prepares chunks of memory to use as buffers. These are used to contain the body of the ethernet frames
- The software provides "Buffer Descriptors", which act as a 'handle' for each buffer. These contain the address and some status bits for each of the buffers
- The software gives the hardware the address of the Buffer Descriptors, which the hardware uses for sending and receiving frames
In this case, the Buffer Descriptors act as the synchronization mechanism between the hardware and software. It is common in embedded rust to consider the hardware as a "separate thread", as it often executes asynchronously from the main software, in this case receiving and sending ethernet frames.
Additionally, it is considered "safest" to use static memory buffers when interacting with DMA, as memory on the stack can suffer from the same kind of "liveness" issues as you have with sharing stack data to multiple threads (basically: if you forget to 'turn off' the DMA before leaving the relevant stack frame: oops! memory corruption!).
For both the Buffer Descriptors and the Buffers themselves, they need to be UnsafeCell
s as the hardware can change the contents of either without interaction from the software. However, this means that I want both a static (for DMA stability/safety), as well as UnsafeCell (for correct behavior in the presence of multiple "thread" r/w access).
Safety is guaranteed by using a certain bit in the buffer descriptor in order to denote "ownership" of a given buffer. For example, when receiving frames:
- The software masks the lowest bit in a descriptor to let the hardware know that buffer is ready to be written to (by the hardware).
- The software then waits until the descriptor's lowest bit is set, which notes that the hardware has completed the transfer, and is ready for the software to read.
- This means I can safely give the user a reference to the buffer, knowing that the contents are valid, and will not be modified by the hardware
- Once the user is "done" with the buffer, and the handle is dropped, the descriptor is reset to clear the lowest bit, which signifies that access to the relevant buffer should not be allowed (it may be spuriously mutated at any time).
This is (admittedly) a long-winded example of the same situation/use-case that @m-ou-se described, using an atomicbool/bit to synchronize access, but this is a decently common pattern in the embedded world, and leads to a swath of unsafe impl {Sync|Send} definitions, which I think would better communicate intent with a dedicated SyncUnsafeCell
type.