mapped_clock/lib.rs
1// Copyright 2025 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
5//! Implements a clock backed by memory mapped into this process' virtual address
6//! space. See [MappedClock] for details.
7
8use zx_status::Status;
9
10/// The size of the memory region used by the memory mapped clock. While in theory this size could
11/// change in future Fuchsia releases, in practice it is likely never going to. So we expose it
12/// as a constant.
13pub const CLOCK_SIZE: usize = 4096;
14
15/// A clock backed by memory mapped into this process' virtual address space.
16///
17/// A memory mapped clock can be read more efficiently than a regular kernel
18/// clock object in contexts where making syscalls are undesirable, for example
19/// for efficiency reasons.
20///
21/// A memory mapped clock will clean up after itself when going out of scope.
22///
23/// To create one, you will need a [zx::Clock], a [zx::Vmar] and a call to
24/// [MappedClock::try_new].
25#[derive(Debug)]
26pub struct MappedClock<Reference: zx::Timeline, Output: zx::Timeline> {
27 // The address range that the clock is mapped into. We keep a reference
28 // so we can unmap the range when this struct goes out of scope.
29 parent_vmar: zx::Vmar,
30 // The virtual address of the memory mapped clock.
31 addr: usize,
32 // The size of the memory area used by this mappable clock. It is constant
33 // for the lifetime of the clock. This value is set by Zircon.
34 clock_size: usize,
35 // Unmap the clock when dropping MappedClock.
36 unmap_on_drop: bool,
37 _mark: std::marker::PhantomData<(Reference, Output)>,
38}
39
40impl<Reference: zx::Timeline, Output: zx::Timeline> Drop for MappedClock<Reference, Output> {
41 fn drop(&mut self) {
42 if self.unmap_on_drop {
43 unsafe {
44 // SAFETY: try_new ensures `addr` and `clock_size` are correctly initialized and
45 // valid while this struct lives. The only way for the unmap to fail is if vmar
46 // is somehow invalid, or if someone already called unmap for this clock, both of
47 // which require unsafe code, which can not happen by accident.
48 self.parent_vmar
49 .unmap(self.raw_addr(), self.clock_size)
50 .expect("address should be unmappable");
51 }
52 }
53 }
54}
55
56impl<Reference: zx::Timeline, Output: zx::Timeline> MappedClock<Reference, Output> {
57 /// Tries to convert the supplied regular `clock` into a memory mapped clock.
58 ///
59 /// A memory mapped clock can be read more efficiently than a regular kernel
60 /// clock object in contexts where calling into the kernel is undesirable.
61 /// At the same time, updates to the memory mapped clock can be
62 /// observed consistently with any other observers of the same underlying clock,
63 /// a property guaranteed by Zircon.
64 ///
65 /// As a tradeoff, a memory mapped clock may offer a restricted set of methods,
66 /// and has more complex construction and lifecycle as compared to [zx::Clock].
67 ///
68 /// To ensure that there is no confusion as to how the clock is accessed, this
69 /// conversion consumes [zx::Clock], and is not reversible. If you need
70 /// to use [Self] both as a regular and mapped clock, it is probably a good idea
71 /// to call `duplicate_handle()` on [zx::Clock] before calling this method.
72 ///
73 /// # Args
74 ///
75 /// - `clock`: the clock to convert to a mapped clock.
76 /// - `parent_vmar`: a handle to the virtual memory address range to map the clock into.
77 /// The clock will be unmapped when [Self] goes out of scope. Must be cloneable.
78 /// - `vmar_flags`: flags to apply when mapping the clock. Usually this needs to be at least
79 /// `zx::VmarFlags::PERM_READ`.
80 ///
81 /// # Errors
82 ///
83 /// The conversion may fail if `clock` was not created as a mappable clock using
84 /// [ClockOpts::MAPPABLE] at its creation time.
85 pub fn try_new(
86 clock: zx::Clock<Reference, Output>,
87 parent_vmar: &zx::Vmar,
88 vmar_flags: zx::VmarFlags,
89 ) -> Result<MappedClock<Reference, Output>, Status> {
90 Self::try_new_internal(
91 &clock,
92 parent_vmar,
93 vmar_flags,
94 /*unmap_on_drop=*/ true,
95 /*offset=*/ 0,
96 )
97 }
98
99 /// Same as [try_new], but allows mapping with a specified offset.
100 ///
101 /// # Args
102 ///
103 /// Same as [try_new], except:
104 /// - `vmar_flags`: must include `SPECIFIC` if `offset` is not zero.
105 pub fn try_new_with_offset(
106 clock: zx::Clock<Reference, Output>,
107 parent_vmar: &zx::Vmar,
108 vmar_flags: zx::VmarFlags,
109 offset: u64,
110 ) -> Result<MappedClock<Reference, Output>, Status> {
111 Self::try_new_internal(
112 &clock,
113 parent_vmar,
114 vmar_flags,
115 /*unmap_on_drop=*/ true,
116 offset,
117 )
118 }
119
120 /// Same as `try_new`, but does not unmap the clock at end of this struct's lifetime.
121 pub fn try_new_without_unmap(
122 clock: &zx::Clock<Reference, Output>,
123 parent_vmar: &zx::Vmar,
124 vmar_flags: zx::VmarFlags,
125 offset: u64,
126 ) -> Result<MappedClock<Reference, Output>, Status> {
127 Self::try_new_internal(
128 clock,
129 parent_vmar,
130 vmar_flags,
131 /*unmap_on_drop=*/ false,
132 offset,
133 )
134 }
135
136 fn try_new_internal(
137 clock: &zx::Clock<Reference, Output>,
138 parent_vmar: &zx::Vmar,
139 vmar_flags: zx::VmarFlags,
140 unmap_on_drop: bool,
141 offset: u64,
142 ) -> Result<MappedClock<Reference, Output>, Status> {
143 let offset: usize = offset.try_into().map_err(|err| {
144 log::error!(
145 "[{}:{}] could not convert into usize: {offset:?}: {err:?}",
146 file!(),
147 line!()
148 );
149 zx::Status::INTERNAL
150 })?;
151 // The easiest way to ensure we can drop() without affecting the caller
152 // API. Consider changing if it becomes a problem.
153 let parent_vmar = parent_vmar
154 .duplicate_handle(zx::Rights::SAME_RIGHTS)
155 .inspect_err(|err| log::error!("MappedClock: try_new: {err:?}"))?;
156 // Follows the C++ example from:
157 // https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0266_memory_mappable_kernel_clocks
158 let clock_size = zx::Clock::get_mapped_size(&clock)
159 .inspect_err(|err| log::error!("in get_mapped_size: {err:?}"))?;
160 let addr =
161 parent_vmar.map_clock(vmar_flags, offset, &clock, clock_size).inspect_err(|err| {
162 log::error!("MappedClock: map_clock: {err:?}, {parent_vmar:?}, {offset:0x}")
163 })?;
164
165 Ok(Self {
166 parent_vmar,
167 addr,
168 clock_size: CLOCK_SIZE,
169 unmap_on_drop,
170 _mark: std::marker::PhantomData,
171 })
172 }
173
174 /// Returns the raw value of the address this clock is mapped to.
175 pub fn raw_addr(&self) -> usize {
176 self.addr
177 }
178
179 /// The size of the memory region occupied by this memory mapped clock.
180 pub fn size(&self) -> usize {
181 self.clock_size
182 }
183
184 /// Read the clock indication.
185 ///
186 /// This is the same method as on [Clock].
187 pub fn read(&self) -> Result<zx::Instant<Output>, Status> {
188 unsafe {
189 // SAFETY: try_new ensures self.addr is correctly initialized.
190 zx::Clock::<Reference, Output>::read_mapped(self.addr)
191 }
192 }
193
194 /// Get the clock details, such as backtop time and similar.
195 ///
196 /// This is the same method as on [Clock].
197 pub fn get_details(&self) -> Result<zx::ClockDetails<Reference, Output>, Status> {
198 unsafe {
199 // SAFETY: try_new ensures self.addr is correctly initialized.
200 zx::Clock::<Reference, Output>::get_details_mapped(self.addr)
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use fuchsia_runtime as frt;
209
210 #[test]
211 fn try_mapping() {
212 let clock = zx::SyntheticClock::create(
213 zx::ClockOpts::MAPPABLE | zx::ClockOpts::MONOTONIC,
214 Some(zx::SyntheticInstant::from_nanos(42)),
215 )
216 .unwrap();
217 let vmar_root = frt::vmar_root_self();
218 {
219 let clock_clone = clock.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
220 let mapped_clock =
221 MappedClock::try_new(clock_clone, &vmar_root, zx::VmarFlags::PERM_READ).unwrap();
222
223 // Check that the clock details are appropriate.
224 let details = mapped_clock.get_details().unwrap();
225 assert_eq!(zx::SyntheticInstant::from_nanos(42), details.backstop);
226
227 // An unstarted clock is stuck at backstop.
228 let now = mapped_clock.read().unwrap();
229 assert_eq!(zx::SyntheticInstant::from_nanos(42), now);
230 {
231 let _clock_ref = &mapped_clock;
232 // Doesn't explode.
233 let now = _clock_ref.read().unwrap();
234 assert_eq!(zx::SyntheticInstant::from_nanos(42), now);
235 }
236 assert!(mapped_clock.raw_addr() != 0);
237
238 // Unmapped here.
239 }
240 assert_eq!(zx::SyntheticInstant::from_nanos(42), clock.read().unwrap());
241 }
242}