Skip to main content

seq_lock/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use starnix_logging::with_zx_name;
6use std::arch::asm;
7use std::marker::PhantomData;
8use std::mem::{align_of, size_of};
9use std::sync::Arc;
10use std::sync::atomic::AtomicU32;
11use zerocopy::{Immutable, IntoBytes};
12
13const SEQUENCE_SIZE: usize = size_of::<AtomicU32>();
14
15/// Byte size to use when incrementally writing out T in [`set_value()`]. Determined
16/// by the params in T.
17/// Four -> write in u32 chunks.
18/// Eight -> write in u64 chunks, although the first 8 bytes may be two u32s (one
19/// of which is the `sequence`).
20#[derive(PartialEq)]
21pub enum WriteSize {
22    Four,
23    Eight,
24}
25
26/// Types that are safe to be synchronized across address spaces using a Seqlock.
27///
28/// A type implementing this trait can optionally include the sequence as
29/// its first field, indicated by `HAS_INLINE_SEQUENCE`. If it does not, [`SeqLock`]
30/// will place a u32 atomic sequence number in between the header and value.
31///
32/// # Safety
33///
34/// Types implementing this trait guarantee that they can be safely written
35/// to shared memory in chunks of `WRITE_SIZE` without introducing undefined
36/// behavior for concurrent readers in other address spaces.
37pub unsafe trait SeqLockable: IntoBytes + Immutable {
38    /// The chunk size to use when writing to memory, either 4 or 8 bytes.
39    const WRITE_SIZE: WriteSize;
40
41    /// Indicates whether the type includes the u32 sequence as its first field.
42    const HAS_INLINE_SEQUENCE: bool;
43
44    /// Name used to identify the VMO for debugging.
45    const VMO_NAME: &'static [u8];
46}
47
48/// Declare an instance of [`SeqLock`] by supplying header([`H`]) and value([`T`]) types,
49/// which should be configured with C-style layout & alignment.
50/// The value T can optionally include the sequence param as its first field (HAS_INLINE_SEQUENCE).
51/// If you choose not to do that, [`SeqLock`] will place a u32 atomic sequence number
52/// in between the header and value, in a VMO, shifting the value payload by `SEQUENCE_SIZE`.
53pub struct SeqLock<H: IntoBytes + Immutable, T: SeqLockable> {
54    map_addr: usize,
55    readonly_vmo: Arc<zx::Vmo>,
56    _phantom_data: PhantomData<(H, T)>,
57}
58
59impl<H: IntoBytes + Default + Immutable, T: SeqLockable + Default> SeqLock<H, T> {
60    pub fn new_default() -> Result<Self, zx::Status> {
61        Self::new(H::default(), T::default())
62    }
63}
64
65/// Points to the sequence (lock) address.
66/// This is always right after the H struct.
67const fn sequence_offset<H>() -> usize {
68    let offset = size_of::<H>();
69    assert!(offset % align_of::<AtomicU32>() == 0, "Sequence must be correctly aligned");
70    offset
71}
72
73impl<H: IntoBytes + Immutable, T: SeqLockable> SeqLock<H, T> {
74    /// Points to the value address, adding any required padding if `sequence` is not inline.
75    ///
76    /// Example with inline sequence (HAS_INLINE_SEQUENCE = true):
77    ///   H: 0
78    ///   H: 4
79    ///   T: 8 <-- points here, because `sequence` is the first param of T.
80    ///   T: 12
81    ///
82    /// Example without inline sequence (HAS_INLINE_SEQUENCE = false):
83    ///   H: 0
84    ///   H: 4
85    ///   [sequence]: 8
86    ///   T: 12 <-- points here, after the added sequence.
87    ///
88    /// Some implementations (SeLinuxStatusValue) rely on SeqLock to track `sequence`, while
89    /// some others (PerfMetadataValue) track `sequence` in T so that they can refer to it.
90    const fn value_offset() -> usize {
91        let offset = sequence_offset::<H>();
92        assert!(
93            offset % align_of::<T>() == 0,
94            "Value alignment must allow packing without padding"
95        );
96        offset + if T::HAS_INLINE_SEQUENCE { 0 } else { SEQUENCE_SIZE }
97    }
98
99    /// Returns the total size of the VMO required to store the header, value, and sequence.
100    const fn vmo_size() -> usize {
101        Self::value_offset() + size_of::<T>()
102    }
103
104    /// Returns an instance with initial values and a read-only VMO handle.
105    /// May fail if the VMO backing the structure cannot be created, duplicated
106    /// read-only, or mapped.
107    pub fn new(header: H, value: T) -> Result<Self, zx::Status> {
108        // Create a VMO sized to hold the header H, value T, and sequence number.
109        let vmo_size = Self::vmo_size();
110        let writable_vmo = with_zx_name(zx::Vmo::create(vmo_size as u64)?, T::VMO_NAME);
111
112        // SAFETY: This is ok because there are no other references to this memory.
113        return unsafe { Self::new_from_vmo(header, value, writable_vmo) };
114    }
115
116    /// Same as new() except that we can pass in an existing Vmo. This means that the
117    /// first part of the Vmo is a SeqLock.
118    ///
119    /// # Safety
120    ///
121    /// Callers must guarantee that any other references to this memory will
122    /// only make aligned atomic accesses to the sequence offset within the memory
123    /// or to fields of H or T.
124    pub unsafe fn new_from_vmo(
125        header: H,
126        value: T,
127        writable_vmo: zx::Vmo,
128    ) -> Result<Self, zx::Status> {
129        const {
130            let write_size = match T::WRITE_SIZE {
131                WriteSize::Four => size_of::<u32>(),
132                WriteSize::Eight => size_of::<u64>(),
133            };
134            assert!(align_of::<T>() >= write_size, "T must be aligned to the write size");
135            assert!(size_of::<T>() % write_size == 0, "size of T must be a multiple of write size");
136            assert!(
137                Self::value_offset() % write_size == 0,
138                "value_offset must be aligned to the write size"
139            );
140        }
141        let value_offset = Self::value_offset();
142        let vmo_size = Self::vmo_size();
143        // Populate the initial default values.
144        writable_vmo.write(header.as_bytes(), 0)?;
145        writable_vmo.write(value.as_bytes(), value_offset as u64)?;
146
147        // Create a readonly handle to the VMO.
148        let writable_rights = writable_vmo.basic_info()?.rights;
149        let readonly_rights = writable_rights.difference(zx::Rights::WRITE);
150        let readonly_vmo = Arc::new(writable_vmo.duplicate_handle(readonly_rights)?);
151
152        // Map the VMO writable by this object, and populate it.
153        let flags = zx::VmarFlags::PERM_READ
154            | zx::VmarFlags::ALLOW_FAULTS
155            | zx::VmarFlags::REQUIRE_NON_RESIZABLE
156            | zx::VmarFlags::PERM_WRITE;
157
158        let status = Self {
159            map_addr: fuchsia_runtime::vmar_root_self().map(
160                0,
161                &writable_vmo,
162                0,
163                vmo_size,
164                flags,
165            )?,
166            readonly_vmo: readonly_vmo,
167            _phantom_data: PhantomData,
168        };
169        Ok(status)
170    }
171
172    /// Returns a read-only handle to the VMO containing the header, atomic
173    /// sequence number, and value.
174    pub fn get_readonly_vmo(&self) -> Arc<zx::Vmo> {
175        self.readonly_vmo.clone()
176    }
177
178    /// Returns a read-only copy of the value as a T struct object.
179    /// This read occurs with a sequence check to ensure that:
180    ///   1. Someone else is not already in the middle of writing the data
181    ///   2. The data had not been modified during the read
182    pub fn get(&self) -> T {
183        let mut value = std::mem::MaybeUninit::<T>::uninit();
184        let value_ptr = value.as_mut_ptr();
185        let starting_addr = self.map_addr + Self::value_offset();
186        let sequence_addr = self.map_addr + sequence_offset::<H>();
187
188        loop {
189            // Read sequence (lock) value.
190            // SAFETY: We know sequence is u32 hardcoded to sequence_addr.
191            let sequence = unsafe { atomic_load_u32_acquire(sequence_addr as *mut u32) };
192            if sequence % 2 != 0 {
193                std::hint::spin_loop();
194                continue;
195            }
196
197            // Read data in chunks of u32 or u64 depending on the WriteSize for T.
198            if T::WRITE_SIZE == WriteSize::Four {
199                for i in 0..(size_of::<T>() / size_of::<u32>()) {
200                    let addr = starting_addr + i * size_of::<u32>();
201                    // SAFETY: User stated via WriteSize that T is made of u32s.
202                    let val = unsafe { atomic_load_u32_acquire(addr as *mut u32) };
203                    // SAFETY: We know value_ptr points to a T struct param.
204                    unsafe { (value_ptr as *mut u32).add(i).write(val) };
205                }
206            } else if T::WRITE_SIZE == WriteSize::Eight {
207                for i in 0..(size_of::<T>() / size_of::<u64>()) {
208                    let addr = starting_addr + i * size_of::<u64>();
209                    // SAFETY: User stated via WriteSize that T is made of u64s.
210                    let val = unsafe { atomic_load_u64_acquire(addr as *mut u64) };
211                    // SAFETY: We know value_ptr points to a T struct param.
212                    unsafe { (value_ptr as *mut u64).add(i).write(val) };
213                }
214            }
215
216            // Read sequence again to compare with earlier sequence value.
217            // SAFETY: We know sequence is u32 hardcoded to sequence_addr.
218            let current_sequence = unsafe { atomic_load_u32_acquire(sequence_addr as *mut u32) };
219            if sequence != current_sequence {
220                continue;
221            }
222            break;
223        }
224        // Only return after sequence checks are valid, otherwise loops to check again.
225        // SAFETY: By this point the value should be synced and valid. Also we know the
226        // data starting at the offset is a T struct.
227        unsafe { value.assume_init() }
228    }
229
230    /// Updates the value directly. Uses Seqlock pattern.
231    pub fn set_value(&self, value: T) {
232        // All data in <T> must be stored with some form of atomic write.
233        // Given two consecutive writes W1 and W2, it is technically possible for a
234        // client to observe the data written by W2 before observing the
235        // start-increment for W2. The reader observes the same post-W1/pre-W2
236        // sequence number at both start and end of the read, so thinks everything
237        // is consistent, but gets some mix of W1 and W2's data.
238        // In order to synchronize correctly we must either:
239        //
240        // 1) Store all the data with any atomic ordering (i.e. relaxed)
241        // 2) Store all the data with atomic-release
242        // We've chosen to do the second.
243        let starting_addr = self.map_addr + Self::value_offset();
244
245        // Convert T to u8s so that we can process in u32 or u64 chunks.
246        const { assert!(size_of::<T>() % 4 == 0) };
247        let value_as_u8_bytes = value.as_bytes();
248        let value_ptr_in_u32 = value.as_bytes().as_ptr().cast::<u32>();
249
250        // Lock prior to writing.
251        let sequence_addr = (self.map_addr + sequence_offset::<H>()) as *mut u32;
252        // Don't use AtomicU32 fetch_add because it is undefined behavior to
253        // access across mutually distrusting address spaces, which happens for the seq lock.
254        // SAFETY: sequence_addr is a valid pointer because `map_addr` is sized to fit
255        // `H` and `T` and unmapped when `self` is dropped.
256        let old_sequence = unsafe { atomic_fetch_add_u32_acq_rel(sequence_addr, 1) };
257        // Old `sequence` value must always be even (i.e. unlocked) before writing.
258        assert!((old_sequence % 2) == 0, "expected sequence to be unlocked");
259
260        // Process and write to memory in u32 or u64 chunks.
261        const { assert!(align_of::<T>() == 4 || align_of::<T>() == 8) };
262        // If T included the sequence number, we shouldn't write to it
263        // (overwrite it) here. We should just skip it.
264        let mut start_index = 0;
265        if T::HAS_INLINE_SEQUENCE {
266            start_index = 1;
267        }
268
269        if T::WRITE_SIZE == WriteSize::Four {
270            assert!(align_of::<T>() == 4);
271            for i in start_index..(value_as_u8_bytes.len() / size_of::<u32>()) {
272                let current_value_addr = starting_addr + (i * size_of::<u32>());
273                // SAFETY: We checked alignment and size above so we know that this points to
274                // the valid current u32 value.
275                let current_value = unsafe { *value_ptr_in_u32.add(i) };
276
277                // Use asm to write u32 chunk so that the values are being written
278                // atomically between address spaces. Don't use std::sync::atomic because that
279                // only syncs writes within the Rust abstract machine.
280                // SAFETY: Caller has verified that no one else is writing to this exact memory, and
281                // that both currrent_value_addr and value_as_u64 are valid.
282                unsafe { atomic_store_u32_release(current_value_addr as *mut u32, current_value) };
283            }
284        } else if T::WRITE_SIZE == WriteSize::Eight {
285            assert!(align_of::<T>() == 8 && size_of::<T>() % 8 == 0);
286
287            // When `WRITE_SIZE` is `Eight`, the memory is 8-byte aligned.
288            // If `HAS_INLINE_SEQUENCE` is true, the 4-byte sequence lock occupies the
289            // first half of an 8-byte block. We must skip that 4-byte sequence, perform a
290            // 4-byte store for the remainder of that block, and then proceed with 8-byte stores.
291            let mut offset_index = 0;
292
293            if start_index == 1 {
294                // Skip first u32 (sequence). Write next u32.
295                let addr = starting_addr + (start_index * size_of::<u32>());
296                // SAFETY: As a `SeqLockable`, the caller guarantees via `HAS_INLINE_SEQUENCE` that
297                // the u32 sequence spans the first half of the 8-byte aligned block. This means that
298                // getting the next u32 value (to sum up to a complete u64) is safe.
299                let value = unsafe { *value_ptr_in_u32.add(start_index) };
300                // SAFETY: Caller has verified that no one else is writing to this exact memory, and
301                // that both addr and value are valid.
302                unsafe { atomic_store_u32_release(addr as *mut u32, value) };
303
304                offset_index += 1;
305            }
306
307            // Write the rest of the data using 8-byte stores.
308            let value_ptr_in_u64 = value.as_bytes().as_ptr().cast::<u64>();
309            for i in offset_index..(value_as_u8_bytes.len() / size_of::<u64>()) {
310                let addr = starting_addr + (i * size_of::<u64>());
311                // SAFETY: We checked alignment and size above so we know that this points to
312                // the valid current u64 value.
313                let value = unsafe { *value_ptr_in_u64.add(i) };
314
315                // Use asm to write u64 chunk so that the values are being written
316                // atomically between address spaces. Don't use std::sync::atomic because that
317                // only syncs writes within the Rust abstract machine.
318                // SAFETY: Caller has verified that no one else is writing to this exact memory, and
319                // that both addr and value are valid.
320                unsafe { atomic_store_u64_release(addr as *mut u64, value) };
321            }
322        }
323
324        // Unlock after all writing is done.
325        // SAFETY: sequence_addr is a valid pointer as per above SAFETY comment.
326        let _ = unsafe { atomic_fetch_add_u32_acq_rel(sequence_addr, 1) };
327    }
328
329    /// Retrieves the memory address of the beginning of the handle part of the VMO.
330    /// You can use this to point to a param you want to edit (e.g. with an offset).
331    pub fn get_map_address(&mut self) -> *const T {
332        let address = self.map_addr;
333        return std::ptr::with_exposed_provenance::<T>(address);
334    }
335}
336
337/// This performs an atomic store-release of a 32-bit value to `addr`.
338/// Use this if you have a u32 or your struct is align(4).
339///
340/// Rust's memory model defines how atomics work across threads, but
341/// doesn't account for the way Starnix handles access across mutually distrusting
342/// address spaces.
343/// This Seqlock is intended to be mapped and read by different address spaces. Rust's
344/// guarantees do not apply and reading across these address spaces is undefined behavior.
345/// Theoretically the Rust compiler could determine that the atomic is never read
346/// from within the process and optimize out the store. We work around this by directly
347/// including the assembly an atomic would generate to prevent the compiler from
348/// "helpfully" optimizing it away.
349///
350/// # Safety
351///
352/// 1. The caller must ensure `addr` points to an address ptr that is valid and 4-byte
353///    aligned. The `addr` must be writable by the current process.
354/// 2. The caller must ensure that no other non-atomic operations are
355///    occurring on this memory address simultaneously.
356pub unsafe fn atomic_store_u32_release(addr: *mut u32, value: u32) {
357    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64")))]
358    compile_error!("This architecture is not supported");
359
360    // SAFETY: Caller must provide a valid `addr` and `value` as defined in the # Safety
361    // section above. The asm directly stores the value to that ptr. The original value
362    // may not have been a u32 (e.g. it's a SeLinuxStatusValue struct); caller is
363    // responsible to break struct into valid u32 chunks.
364    unsafe {
365        #[cfg(target_arch = "x86_64")]
366        {
367            asm!(
368                "mov [{addr}], {val:e}",
369                addr = in(reg) addr,
370                val = in(reg) value,
371                options(nostack, preserves_flags)
372            );
373        }
374        #[cfg(target_arch = "aarch64")]
375        {
376            asm!(
377                "stlr {val:w}, [{addr}]",
378                addr = in(reg) addr,
379                val = in(reg) value,
380                options(nostack, preserves_flags)
381            );
382        }
383        #[cfg(target_arch = "riscv64")]
384        {
385            asm!(
386                "fence rw, w",
387                "sw {val}, 0({addr})",
388                addr = in(reg) addr,
389                val = in(reg) value,
390                options(nostack, preserves_flags)
391            );
392        }
393    }
394}
395
396/// This performs an atomic fetch-add with Acquire and Release ordering of `val`
397/// to a 32-bit value at `addr`. Use this to update the u32 lock.
398///
399/// Rust's memory model defines how atomics work across threads, but
400/// doesn't account for the way Starnix handles access across mutually distrusting
401/// address spaces.
402/// This Seqlock is intended to be mapped and read by different address spaces. Rust's
403/// guarantees do not apply and reading across these address spaces is undefined behavior.
404/// Theoretically the Rust compiler could determine that the atomic is never read
405/// from within the process and optimize out the store. We work around this by directly
406/// including the assembly an atomic would generate to prevent the compiler from
407/// "helpfully" optimizing it away.
408///
409/// # Safety
410/// The caller must ensure `addr` is valid. The `addr` must be writable by the current process.
411pub unsafe fn atomic_fetch_add_u32_acq_rel(addr: *mut u32, value: u32) -> u32 {
412    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64")))]
413    compile_error!("This architecture is not supported");
414
415    let old_value: u32;
416    // SAFETY: Caller must provide a valid `addr` and `value`. The asm directly
417    // updates the value at that ptr.
418    unsafe {
419        #[cfg(target_arch = "x86_64")]
420        {
421            asm!(
422                "lock xadd [{addr}], {val:e}",
423                addr = in(reg) addr,
424                val = inout(reg) value => old_value,
425                options(nostack),
426            );
427        }
428        #[cfg(target_arch = "aarch64")]
429        {
430            asm!(
431                "1:",
432                "ldaxr {old:w}, [{addr}]",
433                "add {tmp:w}, {old:w}, {val:w}",
434                "stlxr {status:w}, {tmp:w}, [{addr}]",
435                "cbnz {status:w}, 1b",
436                addr = in(reg) addr,
437                val = in(reg) value,
438                old = out(reg) old_value,
439                tmp = out(reg) _,
440                status = out(reg) _,
441                options(nostack),
442            );
443        }
444        #[cfg(target_arch = "riscv64")]
445        {
446            asm!(
447                "amoadd.w.aqrl {old}, {val}, ({addr})",
448                addr = in(reg) addr,
449                val = in(reg) value,
450                old = out(reg) old_value,
451                options(nostack),
452            );
453        }
454    }
455    old_value
456}
457
458/// This performs an atomic store-release of a 64-bit value to `addr`.
459/// Use this if you have a u64 or your struct is align(8).
460///
461/// Rust's memory model defines how atomics work across threads, but
462/// doesn't account for the way Starnix handles access across mutually distrusting
463/// address spaces.
464/// This Seqlock is intended to be mapped and read by different address spaces. Rust's
465/// guarantees do not apply and reading across these address spaces is undefined behavior.
466/// Theoretically the Rust compiler could determine that the atomic is never read
467/// from within the process and optimize out the store. We work around this by directly
468/// including the assembly an atomic would generate to prevent the compiler from
469/// "helpfully" optimizing it away.
470///
471/// # Safety
472///
473/// 1. The caller must ensure `addr` points to an address ptr that is valid and 8-byte
474///    aligned. The `addr` must be writable by the current process.
475/// 2. The caller must ensure that no other non-atomic operations are
476///    occurring on this memory address simultaneously.
477pub unsafe fn atomic_store_u64_release(addr: *mut u64, value: u64) {
478    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64")))]
479    compile_error!("This architecture is not supported");
480
481    // SAFETY: Caller must provide a valid `addr` and `value` as defined in the # Safety
482    // section above. The asm directly stores the value to that ptr. The original value
483    // may not have been a u64 (e.g. it's a PerfMetadataValue struct); caller is
484    // responsible to break struct into valid u64 chunks.
485    unsafe {
486        #[cfg(target_arch = "x86_64")]
487        {
488            asm!(
489                "mov [{addr}], {val}",
490                addr = in(reg) addr,
491                val = in(reg) value,
492                options(nostack, preserves_flags)
493            );
494        }
495        #[cfg(target_arch = "aarch64")]
496        {
497            asm!(
498                // Add memory barrier.
499                "dmb ishst",
500                // Use str instead of stlr to explicitly write only.
501                // Otherwise stlr attempts to read first and we don't have permissions.
502                "str {val}, [{addr}]",
503                addr = in(reg) addr,
504                val = in(reg) value,
505                options(nostack, preserves_flags)
506            );
507        }
508        #[cfg(target_arch = "riscv64")]
509        {
510            asm!(
511                "fence rw, w",
512                "sd {val}, 0({addr})",
513                addr = in(reg) addr,
514                val = in(reg) value,
515                options(nostack, preserves_flags)
516            );
517        }
518    }
519}
520
521/// Performs an atomic acquire (load, or read) of a u32 from `addr`.
522/// You can use this to read the `sequence` or `lock` value.
523///
524/// # Safety
525/// `addr` must point to a valid address and be 4-byte aligned.
526pub unsafe fn atomic_load_u32_acquire(addr: *mut u32) -> u32 {
527    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64")))]
528    compile_error!("This architecture is not supported");
529
530    let value: u32;
531    // SAFETY: addr must be a valid pointer and 4-byte aligned.
532    unsafe {
533        #[cfg(target_arch = "x86_64")]
534        {
535            asm!(
536                "mov {val:e}, [{ptr}]",
537                ptr = in(reg) addr,
538                val = out(reg) value,
539                options(nostack, preserves_flags)
540            );
541        }
542        #[cfg(target_arch = "aarch64")]
543        {
544            asm!(
545                "ldar {val:w}, [{ptr}]",
546                ptr = in(reg) addr,
547                val = out(reg) value,
548                options(nostack, preserves_flags)
549            );
550        }
551        #[cfg(target_arch = "riscv64")]
552        {
553            asm!(
554                "lw {val}, 0({ptr})",
555                "fence r, rw",
556                ptr = in(reg) addr,
557                val = out(reg) value,
558                options(nostack, preserves_flags)
559            );
560        }
561    }
562    value
563}
564
565/// Performs an atomic acquire (load, or read) of a u64 from `addr`.
566///
567/// # Safety
568/// `addr` must point to a valid address and be 8-byte aligned.
569pub unsafe fn atomic_load_u64_acquire(addr: *mut u64) -> u64 {
570    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "riscv64")))]
571    compile_error!("This architecture is not supported");
572
573    let value: u64;
574    // SAFETY: addr must be a valid pointer and 8-byte aligned.
575    unsafe {
576        #[cfg(target_arch = "x86_64")]
577        {
578            asm!(
579                "mov {val}, [{ptr}]",
580                ptr = in(reg) addr,
581                val = out(reg) value,
582                options(nostack, preserves_flags)
583            );
584        }
585        #[cfg(target_arch = "aarch64")]
586        {
587            asm!(
588                "ldar {val}, [{ptr}]",
589                ptr = in(reg) addr,
590                val = out(reg) value,
591                options(nostack, preserves_flags)
592            );
593        }
594        #[cfg(target_arch = "riscv64")]
595        {
596            asm!(
597                "ld {val}, 0({ptr})",
598                "fence r, rw",
599                ptr = in(reg) addr,
600                val = out(reg) value,
601                options(nostack, preserves_flags)
602            );
603        }
604    }
605    value
606}
607
608impl<H: IntoBytes + Immutable, T: SeqLockable> Drop for SeqLock<H, T> {
609    fn drop(&mut self) {
610        // SAFETY: `self` owns the mapping, and does not dispense any references
611        // to it.
612        unsafe {
613            fuchsia_runtime::vmar_root_self()
614                .unmap(self.map_addr, Self::vmo_size())
615                .expect("failed to unmap SeqLock");
616        }
617    }
618}
619#[cfg(test)]
620mod tests {
621    use super::*;
622    use zerocopy::KnownLayout;
623
624    // Example struct that mirrors PerfMetadataValue.
625    #[repr(C)]
626    #[derive(IntoBytes, Immutable, KnownLayout, Copy, Clone, Debug, PartialEq, Default)]
627    struct WriteSizeEightStruct {
628        lock: u32,
629        val1: u32,
630        val2: u64,
631        val3: u64,
632    }
633
634    // SAFETY: This struct is composed of fields that are safe to write
635    // in 8-byte chunks (two u32s and u64s). It is only used for testing.
636    // It emulates a perf_event_value struct.
637    unsafe impl SeqLockable for WriteSizeEightStruct {
638        const WRITE_SIZE: WriteSize = WriteSize::Eight;
639        const HAS_INLINE_SEQUENCE: bool = true;
640        const VMO_NAME: &'static [u8] = b"test:write_size_eight";
641    }
642
643    #[test]
644    fn test_seqlock_gets_align_eight_with_sequence() {
645        let seqlock = SeqLock::<u64, WriteSizeEightStruct>::new(0, WriteSizeEightStruct::default())
646            .expect("failed to create seqlock");
647
648        let val = WriteSizeEightStruct {
649            lock: 0,
650            val1: 42,
651            val2: 123_456_789_012_345_678,
652            val3: 987_654_321_098_765_432,
653        };
654        seqlock.set_value(val);
655
656        let data = seqlock.get();
657        // The 'lock' field was incremented twice by set_value(),
658        // and not incremented for get().
659        assert_eq!(data.lock, 2);
660        assert_eq!(data.val1, val.val1);
661        assert_eq!(data.val2, val.val2);
662        assert_eq!(data.val3, val.val3);
663    }
664
665    // Example struct that mirrors SeLinuxStatusValue.
666    #[repr(C)]
667    #[derive(IntoBytes, Immutable, KnownLayout, Copy, Clone, Debug, PartialEq, Default)]
668    struct WriteSizeFourStruct {
669        val1: u32,
670        val2: u32,
671        val3: u32,
672    }
673
674    // SAFETY: This struct is composed of u32 fields, making it safe
675    // to write in 4-byte chunks. It is only used for testing.
676    // It emulates a SeLinuxStatusValue struct.
677    unsafe impl SeqLockable for WriteSizeFourStruct {
678        const WRITE_SIZE: WriteSize = WriteSize::Four;
679        const HAS_INLINE_SEQUENCE: bool = false;
680        const VMO_NAME: &'static [u8] = b"test:write_size_four";
681    }
682
683    #[test]
684    fn test_seqlock_gets_align_four() {
685        let seqlock = SeqLock::<u32, WriteSizeFourStruct>::new(0, WriteSizeFourStruct::default())
686            .expect("failed to create seqlock");
687
688        let val = WriteSizeFourStruct { val1: 42, val2: 123_456_789, val3: 987_654_321 };
689        seqlock.set_value(val);
690
691        let data = seqlock.get();
692        assert_eq!(data.val1, val.val1);
693        assert_eq!(data.val2, val.val2);
694        assert_eq!(data.val3, val.val3);
695    }
696
697    // Stress test for get() and set_value().
698    // For two threads, get() and set_value() should work on the same piece of memory.
699    // One thread tries to read a lot, and another writes a lot. This test verifies that,
700    // thanks to the seqlock, the data read is correct (didn't get overwritten mid-read).
701    // TODO(https://fxbug.dev/460246292): Handle cases for more than 1 writer thread.
702    #[test]
703    fn test_seqlock_handles_concurrent_gets_and_sets() {
704        let seqlock = std::sync::Arc::new(
705            SeqLock::<u64, WriteSizeEightStruct>::new(0, WriteSizeEightStruct::default())
706                .expect("failed to create seqlock"),
707        );
708
709        let seqlock_clone = std::sync::Arc::clone(&seqlock);
710        let seqlock_clone_2 = std::sync::Arc::clone(&seqlock);
711
712        let barrier = std::sync::Arc::new(std::sync::Barrier::new(2));
713        let barrier_clone = std::sync::Arc::clone(&barrier);
714
715        // Spawn 2 threads that run concurrently.
716        let writer_thread = std::thread::spawn(move || {
717            barrier.wait();
718            let start = std::time::Instant::now();
719            let mut i = 0u32;
720            while start.elapsed() < std::time::Duration::from_millis(200) {
721                let val = WriteSizeEightStruct { lock: 0, val1: i, val2: i as u64, val3: i as u64 };
722                seqlock_clone.set_value(val);
723                i += 1;
724            }
725        });
726        let reader_thread = std::thread::spawn(move || {
727            let mut reads = 0;
728            let mut last_valid_read = 0;
729            barrier_clone.wait();
730            let start = std::time::Instant::now();
731            while start.elapsed() < std::time::Duration::from_millis(200) {
732                let data = seqlock_clone_2.get();
733                // All fields are the same (no mid-read writes).
734                assert_eq!(data.val1 as u64, data.val2);
735                assert_eq!(data.val2, data.val3);
736
737                // The sequence (lock) should be even (completed writes).
738                assert_eq!(data.lock % 2, 0);
739
740                // get() returns the latest value. The latest value might not increment exactly
741                // by 1 each time because the writer thread might have written zero or multiple
742                // times since we last read. So, we just verify that the latest value is higher
743                // than the previous value.
744                assert!(data.val1 >= last_valid_read);
745                last_valid_read = data.val1;
746                reads += 1;
747            }
748            reads
749        });
750
751        // Wait for both threads to finish.
752        writer_thread.join().unwrap();
753        let total_reads = reader_thread.join().unwrap();
754
755        // Check that reading actually happened.
756        assert!(total_reads > 1, "Expected threads to run concurrently");
757
758        // Check that writes actually happened.
759        let final_data = seqlock.get();
760        assert!(final_data.val1 > 0, "Expected some writes to happen");
761        assert_eq!(final_data.val1 as u64, final_data.val2);
762        assert_eq!(final_data.val2, final_data.val3);
763        assert_eq!(final_data.lock % 2, 0, "Sequence lock should be unlocked");
764    }
765}