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