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::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}