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}