Skip to main content

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}