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