Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/base/src/sys/windows/platform_timer_utils.rs
5394 views
1
// Copyright 2022 The ChromiumOS 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
use std::io;
6
use std::sync::OnceLock;
7
use std::thread::sleep;
8
use std::time::Duration;
9
use std::time::Instant;
10
11
use win_util::win32_wide_string;
12
use winapi::shared::minwindef;
13
use winapi::shared::minwindef::PULONG;
14
use winapi::shared::ntdef::NTSTATUS;
15
use winapi::shared::ntdef::ULONG;
16
use winapi::shared::ntstatus::STATUS_NOT_IMPLEMENTED;
17
use winapi::shared::ntstatus::STATUS_SUCCESS;
18
use winapi::um::libloaderapi::GetProcAddress;
19
use winapi::um::libloaderapi::LoadLibraryW;
20
use winapi::um::mmsystem::TIMERR_NOERROR;
21
use winapi::um::timeapi::timeBeginPeriod;
22
use winapi::um::timeapi::timeEndPeriod;
23
use winapi::um::winnt::BOOLEAN;
24
25
use crate::warn;
26
use crate::Error;
27
use crate::Result;
28
29
type NtQueryTimerResolutionFn = extern "system" fn(PULONG, PULONG, PULONG) -> NTSTATUS;
30
type NtSetTimerResolutionFn = extern "system" fn(ULONG, BOOLEAN, PULONG) -> NTSTATUS;
31
32
struct NtTimerFuncs {
33
nt_query_timer_resolution: NtQueryTimerResolutionFn,
34
nt_set_timer_resolution: NtSetTimerResolutionFn,
35
}
36
37
static NT_TIMER_FUNCS: OnceLock<NtTimerFuncs> = OnceLock::new();
38
39
fn init_nt_timer_funcs() -> NtTimerFuncs {
40
// SAFETY: return value is checked.
41
let handle = unsafe { LoadLibraryW(win32_wide_string("ntdll").as_ptr()) };
42
if handle.is_null() {
43
warn!("Failed to load ntdll: {}", Error::last());
44
return NtTimerFuncs {
45
nt_query_timer_resolution: nt_query_timer_resolution_fallback,
46
nt_set_timer_resolution: nt_set_timer_resolution_fallback,
47
};
48
}
49
50
// SAFETY: return value is checked.
51
let query = unsafe { GetProcAddress(handle, c"NtQueryTimerResolution".as_ptr()) };
52
let nt_query_timer_resolution = if query.is_null() {
53
nt_query_timer_resolution_fallback
54
} else {
55
// SAFETY: the function signature matches.
56
unsafe {
57
std::mem::transmute::<*mut minwindef::__some_function, NtQueryTimerResolutionFn>(query)
58
}
59
};
60
61
// SAFETY: return value is checked.
62
let set = unsafe { GetProcAddress(handle, c"NtSetTimerResolution".as_ptr()) };
63
let nt_set_timer_resolution = if set.is_null() {
64
nt_set_timer_resolution_fallback
65
} else {
66
// SAFETY: the function signature matches.
67
unsafe {
68
std::mem::transmute::<*mut minwindef::__some_function, NtSetTimerResolutionFn>(set)
69
}
70
};
71
72
NtTimerFuncs {
73
nt_query_timer_resolution,
74
nt_set_timer_resolution,
75
}
76
}
77
78
// This function is only used if NtQueryTimerResolution() is not available.
79
extern "system" fn nt_query_timer_resolution_fallback(_: PULONG, _: PULONG, _: PULONG) -> NTSTATUS {
80
STATUS_NOT_IMPLEMENTED
81
}
82
83
// This function is only used if NtSetTimerResolution() is not available.
84
extern "system" fn nt_set_timer_resolution_fallback(_: ULONG, _: BOOLEAN, _: PULONG) -> NTSTATUS {
85
STATUS_NOT_IMPLEMENTED
86
}
87
88
/// Returns the resolution of timers on the host (current_res, max_res).
89
pub fn nt_query_timer_resolution() -> Result<(Duration, Duration)> {
90
let funcs = NT_TIMER_FUNCS.get_or_init(init_nt_timer_funcs);
91
92
let mut min_res: u32 = 0;
93
let mut max_res: u32 = 0;
94
let mut current_res: u32 = 0;
95
let ret = (funcs.nt_query_timer_resolution)(
96
&mut min_res as *mut u32,
97
&mut max_res as *mut u32,
98
&mut current_res as *mut u32,
99
);
100
101
if ret != STATUS_SUCCESS {
102
Err(Error::from(io::Error::other(
103
"NtQueryTimerResolution failed",
104
)))
105
} else {
106
Ok((
107
Duration::from_nanos((current_res as u64) * 100),
108
Duration::from_nanos((max_res as u64) * 100),
109
))
110
}
111
}
112
113
pub fn nt_set_timer_resolution(resolution: Duration) -> Result<()> {
114
let funcs = NT_TIMER_FUNCS.get_or_init(init_nt_timer_funcs);
115
116
let requested_res: u32 = (resolution.as_nanos() / 100) as u32;
117
let mut current_res: u32 = 0;
118
let ret = (funcs.nt_set_timer_resolution)(
119
requested_res,
120
1, /* true */
121
&mut current_res as *mut u32,
122
);
123
124
if ret != STATUS_SUCCESS {
125
Err(Error::from(io::Error::other("NtSetTimerResolution failed")))
126
} else {
127
Ok(())
128
}
129
}
130
131
/// Measures the timer resolution by taking the 90th percentile wall time of 1ms sleeps.
132
pub fn measure_timer_resolution() -> Duration {
133
let mut durations = Vec::with_capacity(100);
134
for _ in 0..100 {
135
let start = Instant::now();
136
// Windows cannot support sleeps shorter than 1ms.
137
sleep(Duration::from_millis(1));
138
durations.push(Instant::now() - start);
139
}
140
141
durations.sort();
142
durations[89]
143
}
144
145
/// Note that Durations below 1ms are not supported and will panic.
146
pub fn set_time_period(res: Duration, begin: bool) -> Result<()> {
147
if res.as_millis() < 1 {
148
panic!(
149
"time(Begin|End)Period does not support values below 1ms, but {res:?} was requested."
150
);
151
}
152
if res.as_millis() > u32::MAX as u128 {
153
panic!("time(Begin|End)Period does not support values above u32::MAX.",);
154
}
155
156
let ret = if begin {
157
// SAFETY: Trivially safe. Note that the casts are safe because we know res is within u32's
158
// range.
159
unsafe { timeBeginPeriod(res.as_millis() as u32) }
160
} else {
161
// SAFETY: Trivially safe. Note that the casts are safe because we know res is within u32's
162
// range.
163
unsafe { timeEndPeriod(res.as_millis() as u32) }
164
};
165
if ret != TIMERR_NOERROR {
166
// These functions only have two return codes: NOERROR and NOCANDO.
167
Err(Error::from(io::Error::new(
168
io::ErrorKind::InvalidInput,
169
"timeBegin/EndPeriod failed",
170
)))
171
} else {
172
Ok(())
173
}
174
}
175
176
/// Note that these tests cannot run on Kokoro due to random slowness in that environment.
177
#[cfg(test)]
178
mod tests {
179
use super::*;
180
181
/// We're testing whether NtSetTimerResolution does what it says on the tin.
182
#[test]
183
#[ignore]
184
fn setting_nt_timer_resolution_changes_resolution() {
185
let (old_res, _) = nt_query_timer_resolution().unwrap();
186
187
nt_set_timer_resolution(Duration::from_millis(1)).unwrap();
188
assert_res_within_bound(measure_timer_resolution());
189
nt_set_timer_resolution(old_res).unwrap();
190
}
191
192
#[test]
193
#[ignore]
194
fn setting_timer_resolution_changes_resolution() {
195
let res = Duration::from_millis(1);
196
197
set_time_period(res, true).unwrap();
198
assert_res_within_bound(measure_timer_resolution());
199
set_time_period(res, false).unwrap();
200
}
201
202
fn assert_res_within_bound(actual_res: Duration) {
203
assert!(
204
actual_res <= Duration::from_millis(2),
205
"actual_res was {actual_res:?}, expected <= 2ms"
206
);
207
}
208
}
209
210