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