1use fuchsia_sync::Mutex;
6use std::mem;
7use std::ptr::NonNull;
8use std::sync::LazyLock;
9use zx_types::zx_rseq_t;
10
11const MAX_THREADS: u64 = 100_000;
16
17#[derive(Debug, Default, Clone, Copy)]
19struct ThreadSlot {
20 offset: u64,
22}
23
24struct Allocator {
26 vmo: zx::Vmo,
28 mapped_addr: usize,
30 free_list: Vec<ThreadSlot>,
32 next_slot_index: u64,
34 stride: u64,
36}
37
38fn round_up_to_page_size(size: usize) -> usize {
40 let page_size = zx::system_get_page_size() as usize;
41 ((size + page_size - 1) / page_size) * page_size
42}
43
44impl Allocator {
45 fn new() -> Self {
47 let stride = std::cmp::max(
48 zx::system_get_dcache_line_size() as u64,
49 mem::size_of::<zx_rseq_t>() as u64,
50 );
51 let needed_size = (MAX_THREADS as usize) * (stride as usize);
52 let map_size = round_up_to_page_size(needed_size);
53 let vmo = zx::Vmo::create(map_size as u64).expect("failed to create RSEQ VMO");
54 let flags = zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE;
55 let mapped_addr = fuchsia_runtime::vmar_root_self()
56 .map(0, &vmo, 0, map_size, flags)
57 .expect("failed to map RSEQ VMO");
58
59 Self { vmo, mapped_addr, free_list: Vec::new(), next_slot_index: 0, stride }
60 }
61
62 fn allocate(&mut self) -> ThreadSlot {
68 if let Some(slot) = self.free_list.pop() {
69 return slot;
70 }
71
72 assert!(self.next_slot_index < MAX_THREADS, "RSEQ max thread count exceeded");
73 let index = self.next_slot_index;
74 self.next_slot_index += 1;
75 let offset = index * self.stride;
76
77 ThreadSlot { offset }
78 }
79
80 fn free(&mut self, abi: *mut zx_rseq_t) {
82 self.free_list.push(self.slot(abi));
83 }
84
85 fn vmo_handle(&self) -> zx::sys::zx_handle_t {
87 self.vmo.raw_handle()
88 }
89
90 fn slot(&self, abi: *mut zx_rseq_t) -> ThreadSlot {
92 let offset = (abi as usize - self.mapped_addr) as u64;
93 ThreadSlot { offset }
94 }
95
96 fn abi(&self, slot: &ThreadSlot) -> *mut zx_rseq_t {
98 (self.mapped_addr + slot.offset as usize) as *mut zx_rseq_t
99 }
100}
101
102static ALLOCATOR: LazyLock<Mutex<Allocator>> = LazyLock::new(|| Mutex::new(Allocator::new()));
103
104thread_local! {
105 static RSEQ: std::cell::Cell<*mut zx_rseq_t> =
111 const { std::cell::Cell::new(std::ptr::null_mut()) };
112}
113
114#[derive(Debug, Clone, Copy)]
120pub struct Rseq {
121 abi: NonNull<zx_rseq_t>,
122}
123
124impl Rseq {
125 #[inline]
137 pub unsafe fn get() -> Self {
138 let abi = NonNull::new(RSEQ.with(|rseq| rseq.get())).expect("thread not registered");
139 Self { abi }
140 }
141
142 #[inline]
149 pub unsafe fn try_get() -> Option<Self> {
150 let abi = NonNull::new(RSEQ.with(|rseq| rseq.get()))?;
151 Some(Self { abi })
152 }
153
154 #[inline]
161 pub fn as_ptr(&self) -> *mut zx_rseq_t {
162 self.abi.as_ptr()
163 }
164
165 #[inline]
174 pub unsafe fn current_cpu(&self) -> u32 {
175 let abi = self.as_ptr();
176 unsafe {
177 let cpu_id_ptr: *mut u32 = std::ptr::addr_of_mut!((*abi).cpu_id);
178 std::ptr::read_volatile(cpu_id_ptr)
181 }
182 }
183
184 #[must_use = "The critical section is active only until the returned `RseqScope` is dropped."]
194 pub unsafe fn activate(&self, critical_section: RseqCriticalSection) -> RseqScope {
195 self.set_critical_section(critical_section);
196 RseqScope { rseq: *self }
197 }
198
199 fn set_critical_section(&self, critical_section: RseqCriticalSection) {
200 let abi = self.as_ptr();
201 unsafe {
202 let start_ip_ptr: *mut u64 = std::ptr::addr_of_mut!((*abi).start_ip);
203 let post_commit_offset_ptr: *mut u64 =
204 std::ptr::addr_of_mut!((*abi).post_commit_offset);
205 let abort_ip_ptr: *mut u64 = std::ptr::addr_of_mut!((*abi).abort_ip);
206
207 std::ptr::write_volatile(post_commit_offset_ptr, 0);
214 std::ptr::write_volatile(start_ip_ptr, critical_section.start_ip);
215 std::ptr::write_volatile(abort_ip_ptr, critical_section.abort_ip);
216 std::ptr::write_volatile(post_commit_offset_ptr, critical_section.post_commit_offset);
217 }
218 }
219}
220
221#[derive(Debug, Default, Clone, Copy)]
223pub struct RseqCriticalSection {
224 start_ip: u64,
226
227 post_commit_offset: u64,
229
230 abort_ip: u64,
232}
233
234impl RseqCriticalSection {
235 pub unsafe fn new(start_ip: u64, post_commit_offset: u64, abort_ip: u64) -> Self {
242 Self { start_ip, post_commit_offset, abort_ip }
243 }
244}
245
246pub struct RseqScope {
248 rseq: Rseq,
249}
250
251impl Drop for RseqScope {
252 fn drop(&mut self) {
253 self.rseq.set_critical_section(RseqCriticalSection::default());
254 }
255}
256
257pub fn rseq_register_thread() -> Rseq {
266 RSEQ.with(|rseq| {
267 assert!(rseq.get().is_null(), "thread already registered");
268 });
269
270 let (vmo_handle, slot, abi) = {
271 let mut allocator = ALLOCATOR.lock();
272
273 let vmo_handle = allocator.vmo_handle();
274 let slot = allocator.allocate();
275 let abi = allocator.abi(&slot);
276
277 (vmo_handle, slot, abi)
278 };
279
280 let status = unsafe {
281 zx::sys::zx_thread_set_rseq(vmo_handle, slot.offset, mem::size_of::<zx_rseq_t>() as u64)
282 };
283
284 zx::Status::ok(status).expect("failed to register thread for RSEQ");
285
286 RSEQ.with(|rseq| {
287 rseq.set(abi);
288 });
289
290 Rseq { abi: NonNull::new(abi).expect("RSEQ slot pointer must be non-null") }
291}
292
293pub fn rseq_register_thread_with_cs(critical_section: RseqCriticalSection) -> Rseq {
302 let rseq = rseq_register_thread();
303 rseq.set_critical_section(critical_section);
304 rseq
305}
306
307pub unsafe fn rseq_unregister_thread() {
318 let abi = RSEQ.with(|rseq| rseq.take());
319 assert!(!abi.is_null(), "thread not registered");
320
321 let status = unsafe { zx::sys::zx_thread_set_rseq(0, 0, 0) };
323 zx::Status::ok(status).expect("failed to unregister thread from RSEQ");
324
325 unsafe {
329 std::ptr::write_volatile(abi, mem::zeroed());
330 }
331
332 ALLOCATOR.lock().free(abi);
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use std::arch::global_asm;
339
340 unsafe extern "C" {
341 fn rseq_test_critical_section(counters: *mut u64, rseq_ptr: *mut zx_rseq_t);
342 static rseq_test_start: u8;
343 static rseq_test_end: u8;
344 }
345
346 #[cfg(target_arch = "x86_64")]
347 global_asm!(
348 ".globl rseq_test_critical_section",
349 ".globl rseq_test_start",
350 ".globl rseq_test_end",
351 "rseq_test_critical_section:",
352 "rseq_test_start:",
353 "mov ecx, [rsi]", "shl rcx, 3", "add rcx, rdi", "inc qword ptr [rcx]", "rseq_test_end:",
359 "ret",
360 );
361
362 #[cfg(target_arch = "aarch64")]
363 global_asm!(
364 ".globl rseq_test_critical_section",
365 ".globl rseq_test_start",
366 ".globl rseq_test_end",
367 "rseq_test_critical_section:",
368 "rseq_test_start:",
369 "ldr w2, [x1]", "lsl x2, x2, #3", "add x2, x0, x2", "ldr x3, [x2]",
374 "add x3, x3, #1",
375 "str x3, [x2]",
376 "rseq_test_end:",
377 "ret",
378 );
379
380 #[cfg(target_arch = "riscv64")]
381 global_asm!(
382 ".globl rseq_test_critical_section",
383 ".globl rseq_test_start",
384 ".globl rseq_test_end",
385 "rseq_test_critical_section:",
386 "rseq_test_start:",
387 "lw t0, 0(a1)", "slli t0, t0, 3", "add t0, a0, t0", "ld t1, 0(t0)",
392 "addi t1, t1, 1",
393 "sd t1, 0(t0)",
394 "rseq_test_end:",
395 "ret",
396 );
397
398 struct TestRegistrationGuard;
399
400 impl TestRegistrationGuard {
401 fn new() -> Self {
402 rseq_register_thread();
403 Self
404 }
405 }
406
407 impl Drop for TestRegistrationGuard {
408 fn drop(&mut self) {
409 RSEQ.with(|rseq| {
410 if !rseq.get().is_null() {
411 unsafe {
414 rseq_unregister_thread();
415 }
416 }
417 });
418 }
419 }
420
421 #[test]
422 #[should_panic = "thread already registered"]
423 fn test_double_registration_panics() {
424 let _guard = TestRegistrationGuard::new();
425 rseq_register_thread();
426 }
427
428 #[test]
429 #[should_panic = "thread not registered"]
430 fn test_unregistered_unregister_panics() {
431 unsafe {
433 rseq_unregister_thread();
434 }
435 }
436
437 #[test]
438 fn test_current_cpu() {
439 let _guard = TestRegistrationGuard::new();
440 unsafe {
441 let rseq = Rseq::get();
442 let cpu_id = rseq.current_cpu();
443 assert_ne!(cpu_id, zx_types::ZX_INFO_INVALID_CPU);
444 }
445 }
446
447 #[test]
448 fn test_activate_field_propagation() {
449 let _guard = TestRegistrationGuard::new();
450 let rseq = unsafe { Rseq::get() };
451 let cs = unsafe { RseqCriticalSection::new(10, 20, 30) };
452
453 let scope = unsafe { rseq.activate(cs) };
454 let abi = rseq.as_ptr();
455 unsafe {
456 assert_eq!((*abi).start_ip, 10);
457 assert_eq!((*abi).abort_ip, 30);
458 assert_eq!((*abi).post_commit_offset, 20);
459 }
460
461 drop(scope);
462 unsafe {
463 assert_eq!((*abi).start_ip, 0);
464 assert_eq!((*abi).abort_ip, 0);
465 assert_eq!((*abi).post_commit_offset, 0);
466 }
467 }
468
469 #[test]
470 fn test_rseq_per_cpu_counter() {
471 let _guard = TestRegistrationGuard::new();
472
473 let rseq = unsafe { Rseq::get() };
474 let cpu_count = zx::system_get_num_cpus();
475 let mut counters = vec![0u64; cpu_count as usize];
476
477 unsafe {
478 let start = &rseq_test_start as *const u8 as u64;
479 let end = &rseq_test_end as *const u8 as u64;
480
481 let cs = RseqCriticalSection::new(start, end - start, start);
482 let _scope = rseq.activate(cs);
483
484 rseq_test_critical_section(counters.as_mut_ptr(), rseq.as_ptr());
486 }
487
488 let sum = counters.iter().sum::<u64>();
489 assert_eq!(sum, 1, "Sum of counters should be 1");
490 }
491
492 #[test]
493 fn test_rseq_packing_and_reuse() {
494 for _ in 0..10 {
495 let _guard = TestRegistrationGuard::new();
496 }
497
498 let threads: Vec<_> = (0..200)
499 .map(|_| {
500 std::thread::spawn(|| {
501 let _guard = TestRegistrationGuard::new();
502 let cpu_id = unsafe { Rseq::get().current_cpu() };
503 assert_ne!(cpu_id, zx_types::ZX_INFO_INVALID_CPU);
504 })
505 })
506 .collect();
507
508 for t in threads {
509 t.join().unwrap();
510 }
511 }
512}