Skip to main content

starnix_core/time/
utc.rs

1// Copyright 2023 The Fuchsia Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Starnix-specific UTC clock implementation.
6//!
7//! UTC clock behaves differently in Fuchsia to what Starnix programs expect. This module abstracts
8//! the differences away. It provides a UTC clock that always runs. In contrast to Fuchsia UTC
9//! clock, which gets started only when the system is reasonably confident that the clock reading
10//! is accurate.
11//!
12//! The paths in this module are somewhat hot, so we document typical measured performance in order
13//! to remain performance-aware in this code. Assume that all the performance notes are made using
14//! the same baseline device. If you need to add or change performance notes, verify first how far
15//! removed your device is from the baseline.
16//!
17//! Starnix UTC clock is started from [backstop][ff] on initialization, and jumps to actual UTC once
18//! Fuchsia provides actual UTC value.
19//!
20//! Consult the [Fuchsia UTC clock specification][ff] for details about UTC clock behavior
21//! specifically on Fuchsia.
22//!
23//! [ff]: https://fuchsia.dev/fuchsia-src/concepts/kernel/time/utc/behavior#differences_from_other_operating_systems
24
25use fidl_fuchsia_time as fftime;
26use fuchsia_component::client::connect_to_protocol_sync;
27use fuchsia_runtime::{UtcClock as UtcClockHandle, UtcClockTransform, UtcInstant, UtcTimeline};
28use mapped_clock::MappedClock;
29use starnix_logging::{log_info, log_warn};
30use std::sync::LazyLock;
31use zx::{Rights, Unowned};
32
33type MemoryMappedClock = MappedClock<zx::BootTimeline, fuchsia_runtime::UtcTimeline>;
34
35/// The basic rights to use when creating or duplicating a UTC clock. Restrict these
36/// on a case-by-case basis only.
37///
38/// Rights:
39///
40/// - `Rights::DUPLICATE`, `Rights::TRANSFER`: used to forward the UTC clock in runners.
41/// - `Rights::READ`: used to read the clock indication.
42/// - `Rights::WAIT`: used to wait on signals such as "clock is updated" or "clock is started".
43/// - `Rights::MAP`, `Rights::INSPECT`: used to memory-map the UTC clock.
44///
45/// The `Rights::WRITE` is notably absent, since on Fuchsia this right is given to particular
46/// components only and a writable clock can not be obtained via procargs.
47pub static UTC_CLOCK_BASIC_RIGHTS: std::sync::LazyLock<zx::Rights> =
48    std::sync::LazyLock::new(|| {
49        Rights::DUPLICATE
50            | Rights::READ
51            | Rights::WAIT
52            | Rights::TRANSFER
53            | Rights::MAP
54            | Rights::INSPECT
55    });
56
57// Stores a vendored handle from a test fixture. In normal operation the value here must be
58// `None`. In some Starnix container tests, we inject a custom UTC clock that the tests
59// manipulate. This is a very special circumstance, so we log warnings accordingly.
60static VENDORED_UTC_HANDLE_FOR_TESTS: LazyLock<Option<UtcClockHandle>> = LazyLock::new(|| {
61    connect_to_protocol_sync::<fftime::MaintenanceMarker>()
62        .inspect_err(|err| {
63            log_info!("could not connect to fuchsia.time.Maintenance, this is expected to work only in special test code: {err:?}");
64        })
65        .map(|proxy: fftime::MaintenanceSynchronousProxy| {
66            // Even in test code, the handle we obtain here will typically not be writable. The
67            // test fixture will ensure this is the case.
68            proxy.get_writable_utc_clock(zx::MonotonicInstant::INFINITE)
69            .inspect_err(|err| {log_warn!("while getting UTC clock: {err:?}");})
70            .map(|handle: zx::Clock| {
71                // Verify that the handle koid matches with the handle koid logged by the UTC vendor component.
72                log_warn!("Starnix kernel is using a vendored UTC handle. This is acceptable ONLY in tests.");
73                log_warn!("Vendored UTC clock handle koid: {:?}", handle.koid());
74                // Make sure to remove unneeded rights, even if we know that the test fixture will
75                // give us proper handle rights.
76                 handle.replace_handle(*UTC_CLOCK_BASIC_RIGHTS)
77                    .map(|handle| handle.cast())
78                    .inspect_err(|err| {
79                        panic!("Could not replace UTC handle for vendored UTC clock: {err:?}");
80                    }).ok()
81            }).unwrap_or(None)
82        }).unwrap_or(None)
83});
84
85fn utc_clock() -> Unowned<'static, UtcClockHandle> {
86    VENDORED_UTC_HANDLE_FOR_TESTS
87        .as_ref()
88        .map(|handle| Unowned::new(handle))
89        .unwrap_or_else(zx_libc::utc::reference_get)
90}
91
92fn duplicate_utc_clock_handle(rights: zx::Rights) -> Result<UtcClockHandle, zx::Status> {
93    utc_clock().duplicate_handle(rights)
94}
95
96// Check whether the UTC clock is started based on actual clock read. If you need something
97// faster, cache the `read` value. Takes about `350ns` to complete.
98fn check_mapped_clock_started(
99    clock: &MemoryMappedClock,
100    backstop: UtcInstant,
101) -> (bool, UtcInstant) {
102    let read = clock.read().expect("clock is readable");
103    (read != backstop, read)
104}
105
106// Returns the details of `clock`.
107// Takes around `500ns`.
108fn get_utc_clock_details(
109    clock: &MemoryMappedClock,
110) -> zx::ClockDetails<zx::BootTimeline, UtcTimeline> {
111    // 500ns.
112    clock.get_details().expect("clock details are readable")
113}
114
115// The implementation of a UTC clock that is offered to programs in a Starnix container.
116//
117// Many Linux APIs need a running UTC clock to function. Since there can be a delay until the UTC
118// clock in Zircon starts up (https://fxbug.dev/42081426), Starnix provides a synthetic utc clock
119// initially, Once the UTC clock is started, the synthetic utc clock is replaced by a real utc
120// clock.
121#[derive(Debug)]
122pub struct UtcClock {
123    // The real underlying Fuchsia UTC clock. This clock may never start,
124    // see module-level documentation for details.
125    real_utc_clock: UtcClockHandle,
126    // The memory mapped clock derived from `real_utc_clock`.
127    // Operations on this clock are up to 3x faster than on the companion
128    // zx::Clock` object.
129    mapped_clock: MemoryMappedClock,
130    // The UTC clock transform from boot timeline to UTC timeline, used while
131    // `real_utc_clock` is not started.  This clock starts from UTC backstop
132    // on boot, and progresses with a nominal 1sec/1sec rate.
133    synthetic_transform: UtcClockTransform,
134    // The UTC backstop value. This is the earliest UTC value that may ever be
135    // shown by any UTC clock in Fuchsia.
136    backstop: UtcInstant,
137}
138
139impl UtcClock {
140    /// Creates a new `UtcClock` instance.
141    ///
142    /// The `real_utc_clock` is a handle to an underlying Fuchsia UTC clock. It will
143    /// be used once started.
144    pub fn new(real_utc_clock: UtcClockHandle) -> Self {
145        let backstop = real_utc_clock.get_details().unwrap().backstop;
146        let synthetic_transform = zx::ClockTransformation {
147            // The boot timeline always starts at zero on boot.
148            reference_offset: zx::BootInstant::ZERO,
149            // By definition, absent other information, a zero reference offset
150            // represents a backstop UTC time instant.
151            synthetic_offset: backstop,
152            // Default rate of 1 synthetic second per 1 reference second disregards
153            // any device variations.
154            rate: zx::sys::zx_clock_rate_t { synthetic_ticks: 1, reference_ticks: 1 },
155        };
156
157        let vmar_parent = fuchsia_runtime::vmar_root_self();
158        let real_utc_clock_clone = real_utc_clock
159            .duplicate_handle(zx::Rights::SAME_RIGHTS)
160            .expect("UTC clock duplication should work");
161        let mapped_clock: MemoryMappedClock =
162            MappedClock::try_new(real_utc_clock_clone, &vmar_parent, zx::VmarFlags::PERM_READ)
163                .expect("failed to map clock into VMAR");
164        let (is_real_utc_clock_started, _) = check_mapped_clock_started(&mapped_clock, backstop);
165        let utc_clock = Self { real_utc_clock, mapped_clock, synthetic_transform, backstop };
166        if !is_real_utc_clock_started {
167            log_warn!(
168                "Waiting for real UTC clock to start, using synthetic clock in the meantime."
169            );
170        }
171        utc_clock
172    }
173
174    fn duplicate_real_utc_clock_handle(
175        &self,
176        rights: zx::Rights,
177    ) -> Result<UtcClockHandle, zx::Status> {
178        self.real_utc_clock.duplicate_handle(rights)
179    }
180
181    /// A slower way to verify whether the real UTC clock has started.
182    ///
183    /// This call takes about `350ns` to complete, refer to the benchmarks
184    /// at `//src/lib/mapped-clock/benchmarks`.
185    fn check_real_utc_clock_started(&self) -> (bool, UtcInstant) {
186        // 350ns.
187        check_mapped_clock_started(&self.mapped_clock, self.backstop)
188    }
189
190    /// Returns the current Starnix UTC time.
191    ///
192    /// In Starnix, UTC time is always running. It is started from backstop
193    /// at Starnix boot, and adjusted to actual UTC once Fuchsia UTC clock
194    /// is started.
195    pub fn now(&self) -> UtcInstant {
196        // 350 ns.
197        let (is_started, utc_now) = self.check_real_utc_clock_started();
198        if is_started {
199            utc_now
200        } else {
201            let boot_time = zx::BootInstant::get();
202            // Utc time is calculated using the same (constant) transform as the one stored in vdso
203            // code. This ensures that the result of `now()` is the same as in
204            // `calculate_utc_time_nsec` in `vdso_calculate_utc.cc`.
205            self.synthetic_transform.apply(boot_time)
206        }
207    }
208
209    /// Estimates the boot time corresponding to `utc`.
210    ///
211    /// # Returns
212    /// - zx::BootInstant: estimated boot time;
213    /// - bool: true if the system UTC clock has been started.
214    ///
215    /// Takes about 900ns worst case.
216    pub fn estimate_boot_time(&self, utc: UtcInstant) -> (zx::BootInstant, bool) {
217        // 350 ns.
218        // Could be reduced on average by caching `started`.
219        let (started, _) = self.check_real_utc_clock_started();
220        let estimated_boot = if started {
221            // 500ns.
222            let details = get_utc_clock_details(&self.mapped_clock);
223            details.reference_to_synthetic.apply_inverse(utc)
224        } else {
225            self.synthetic_transform.apply_inverse(utc)
226        };
227        (estimated_boot, started)
228    }
229}
230
231static UTC_CLOCK: LazyLock<UtcClock> =
232    LazyLock::new(|| UtcClock::new(duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap()));
233
234/// Creates a copy of the UTC clock handle currently in use in Starnix.
235///
236/// Ensure that you are not reading UTC clock for Starnix use from this clock,
237/// use the [utc_now] function instead.
238pub fn duplicate_real_utc_clock_handle() -> Result<UtcClockHandle, zx::Status> {
239    // Maybe reduce rights here?
240    (*UTC_CLOCK).duplicate_real_utc_clock_handle(zx::Rights::SAME_RIGHTS)
241}
242
243/// Returns the current UTC time based on the Starnix UTC clock.
244///
245/// The Starnix UTC clock is always started. This is in contrast to Fuchsia's
246/// UTC clock which may spend an undefined amount of wall-clock time stuck at
247/// [backstop] time reading.
248///
249/// To ensure an uniform reading of the Starnix UTC clock, always use this
250/// function call if you need to know Starnix's view of the current wall time.
251///
252/// [backstop]: https://fuchsia.dev/fuchsia-src/concepts/kernel/time/utc/behavior#differences_from_other_operating_systems
253pub fn utc_now() -> UtcInstant {
254    #[cfg(test)]
255    {
256        if let Some(test_time) = UTC_CLOCK_OVERRIDE_FOR_TESTING
257            .with(|cell| cell.borrow().as_ref().map(|test_clock| test_clock.read().unwrap()))
258        {
259            return test_time;
260        }
261    }
262    (*UTC_CLOCK).now()
263}
264
265/// Estimates the boot time corresponding to `utc`, based on the currently
266/// operating Starnix UTC clock.
267///
268/// # Returns
269/// - zx::BootInstant: estimated boot time;
270/// - bool: true if the system UTC clock has been started.
271pub fn estimate_boot_deadline_from_utc(utc: UtcInstant) -> (zx::BootInstant, bool) {
272    #[cfg(test)]
273    {
274        if let Some(test_time) = UTC_CLOCK_OVERRIDE_FOR_TESTING.with(|cell| {
275            cell.borrow().as_ref().map(|test_clock| {
276                test_clock.get_details().unwrap().reference_to_synthetic.apply_inverse(utc)
277            })
278        }) {
279            return (test_time, true);
280        }
281    }
282    (*UTC_CLOCK).estimate_boot_time(utc)
283}
284
285#[cfg(test)]
286thread_local! {
287    static UTC_CLOCK_OVERRIDE_FOR_TESTING: std::cell::RefCell<Option<UtcClockHandle>> =
288        std::cell::RefCell::new(None);
289}
290
291/// A guard that temporarily overrides the UTC clock for testing.
292///
293/// When this guard is created, it replaces the global UTC clock with a test clock. When the guard
294/// is dropped, the original clock is restored.
295#[cfg(test)]
296pub struct UtcClockOverrideGuard(());
297
298#[cfg(test)]
299impl UtcClockOverrideGuard {
300    /// Creates a new `UtcClockOverrideGuard`.
301    ///
302    /// This function replaces the global UTC clock with `test_clock`. The original clock is
303    /// restored when the returned guard is dropped.
304    pub fn new(test_clock: UtcClockHandle) -> Self {
305        UTC_CLOCK_OVERRIDE_FOR_TESTING.with(|cell| {
306            assert_eq!(*cell.borrow(), None); // We don't expect a previously set clock override when using this type.
307            *cell.borrow_mut() = Some(test_clock);
308        });
309        Self(())
310    }
311}
312
313#[cfg(test)]
314impl Drop for UtcClockOverrideGuard {
315    fn drop(&mut self) {
316        UTC_CLOCK_OVERRIDE_FOR_TESTING.with(|cell| {
317            *cell.borrow_mut() = None;
318        });
319    }
320}