use fuchsia_zircon::{self as zx, AsHandleRef, HandleBased};
use std::{
marker::PhantomData,
mem::{align_of, size_of},
sync::{atomic::AtomicU32, atomic::Ordering, Arc},
};
use zerocopy::{AsBytes, NoCell};
pub struct SeqLock<H: AsBytes + NoCell, T: AsBytes + NoCell> {
map_addr: usize,
readonly_vmo: Arc<zx::Vmo>,
writable_vmo: zx::Vmo,
_phantom_header: PhantomData<H>,
_phantom_value: PhantomData<T>,
}
impl<H: AsBytes + Default + NoCell, T: AsBytes + Default + NoCell> SeqLock<H, T> {
pub fn new_default() -> Result<Self, zx::Status> {
Self::new(H::default(), T::default())
}
}
const fn sequence_offset<H>() -> usize {
let offset = size_of::<H>();
assert!(offset % align_of::<AtomicU32>() == 0, "Sequence must be correctly aligned");
offset
}
const fn value_offset<H, T>() -> usize {
let offset = sequence_offset::<H>() + size_of::<AtomicU32>();
assert!(offset % align_of::<T>() == 0, "Value alignment must allow packing without padding");
offset
}
const fn vmo_size<H, T>() -> usize {
value_offset::<H, T>() + size_of::<T>()
}
impl<H: AsBytes + NoCell, T: AsBytes + NoCell> SeqLock<H, T> {
pub fn new(header: H, value: T) -> Result<Self, zx::Status> {
let writable_vmo = zx::Vmo::create(vmo_size::<H, T>() as u64)?;
writable_vmo.write(header.as_bytes(), 0)?;
writable_vmo.write(value.as_bytes(), value_offset::<H, T>() as u64)?;
let writable_rights = writable_vmo.basic_info()?.rights;
let readonly_rights = writable_rights.difference(zx::Rights::WRITE);
let readonly_vmo = Arc::new(writable_vmo.duplicate_handle(readonly_rights)?);
let flags = zx::VmarFlags::PERM_READ
| zx::VmarFlags::ALLOW_FAULTS
| zx::VmarFlags::REQUIRE_NON_RESIZABLE
| zx::VmarFlags::PERM_WRITE;
let status = Self {
map_addr: fuchsia_runtime::vmar_root_self().map(
0,
&writable_vmo,
0,
vmo_size::<H, T>(),
flags,
)?,
readonly_vmo: readonly_vmo,
writable_vmo: writable_vmo,
_phantom_header: PhantomData,
_phantom_value: PhantomData,
};
Ok(status)
}
pub fn get_readonly_vmo(&self) -> Arc<zx::Vmo> {
self.readonly_vmo.clone()
}
pub fn set_value(&mut self, value: T) {
let sequence =
unsafe { &mut *((self.map_addr + sequence_offset::<H>()) as *mut AtomicU32) };
let old_sequence = sequence.fetch_add(1, Ordering::Relaxed);
assert!((old_sequence % 2) == 0, "expected sequence to be unlocked");
self.writable_vmo.write(value.as_bytes(), value_offset::<H, T>() as u64).unwrap();
sequence.fetch_add(1, Ordering::Release);
}
}
impl<H: AsBytes + NoCell, T: AsBytes + NoCell> Drop for SeqLock<H, T> {
fn drop(&mut self) {
unsafe {
fuchsia_runtime::vmar_root_self()
.unmap(self.map_addr, vmo_size::<H, T>())
.expect("failed to unmap SeqLock");
}
}
}