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