1use crate::fs::sysfs::build_device_directory;
6use crate::task::{CurrentTask, Kernel};
7use crate::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
8use crate::vfs::pseudo::simple_file::{
9 BytesFile, BytesFileOps, SimpleFileNode, parse_unsigned_file,
10};
11use crate::vfs::pseudo::stub_empty_file::StubEmptyFile;
12use crate::vfs::{FsNodeOps, FsString};
13use fidl_fuchsia_hardware_cpu_ctrl as fcpuctrl;
14use fidl_fuchsia_hardware_power_stats as fpowerstats;
15use fidl_fuchsia_power_broker as fbroker;
16use fidl_fuchsia_power_cpu as fcpu;
17use fuchsia_component::client::connect_to_protocol_sync;
18use itertools::Itertools;
19use starnix_logging::{bug_ref, log_warn};
20use starnix_sync::{
21 CpuC2ProxyCacheLock, CpuC2SuspendAccountingLock, CpuFreqProxyCacheLock, LockDepMutex,
22};
23use starnix_uapi::errors::Errno;
24use starnix_uapi::file_mode::mode;
25use starnix_uapi::{errno, error, from_status_like_fdio};
26use std::borrow::Cow;
27use std::collections::HashMap;
28use std::sync::Arc;
29use std::sync::atomic::{AtomicU64, Ordering};
30use zx;
31
32#[derive(Clone)]
33struct CpuFreqDomains(Arc<[Arc<CpuFreqDomain>]>);
34
35pub fn get_cpu_freq_domains(kernel: &Kernel) -> Arc<[Arc<CpuFreqDomain>]> {
38 kernel.expando.get_or_init(|| CpuFreqDomains(get_cpu_domains().into())).0.clone()
39}
40
41pub fn register_cpu_devices(kernel: &Kernel) {
43 let cpu_domains = get_cpu_freq_domains(kernel);
46
47 let mut core_to_domain_map: Vec<(u64, &Arc<CpuFreqDomain>)> = cpu_domains
48 .iter()
49 .flat_map(|domain| domain.core_ids.iter().map(move |id| (*id, domain)))
50 .collect();
51 core_to_domain_map.sort_by_key(|(id, _)| *id);
52 core_to_domain_map.dedup_by_key(|(id, _)| *id);
53
54 let core_count = core_to_domain_map.len();
55 let registry = &kernel.device_registry;
56 let system_device = registry.objects.system_device();
57 let cpu_root =
58 registry.add_subsystemless_device("cpu".into(), Some(system_device), |device, dir| {
59 build_device_directory(device, dir);
60 build_cpu_subsystem_directory(dir, &cpu_domains, core_count);
61 });
62
63 let cpu_bus = registry.objects.get_or_create_bus("cpu".into());
64 let c2_provider = kernel.expando.get::<CpuC2Provider>();
65 for (core_id, domain) in &core_to_domain_map {
66 let name: FsString = format!("cpu{}", core_id).into();
67 let c2_provider = c2_provider.clone();
68 registry.add_bus_device(
69 name.as_ref(),
70 Some(cpu_root.clone()),
71 cpu_bus.clone(),
72 |device, dir| {
73 build_device_directory(device, dir);
74 build_cpu_directory(dir, *core_id, domain, c2_provider);
75 },
76 );
77 }
78}
79
80fn build_cpu_subsystem_directory(
82 dir: &SimpleDirectoryMutator,
83 cpu_domains: &[Arc<CpuFreqDomain>],
84 core_count: usize,
85) {
86 dir.entry(
87 "online",
88 BytesFile::new_node(format!("0-{}\n", core_count.saturating_sub(1)).into_bytes()),
89 mode!(IFREG, 0o444),
90 );
91 dir.entry(
92 "possible",
93 BytesFile::new_node(format!("0-{}\n", core_count.saturating_sub(1)).into_bytes()),
94 mode!(IFREG, 0o444),
95 );
96 dir.subdir("vulnerabilities", 0o755, |dir| {
97 for (name, contents) in VULNERABILITIES {
98 let contents = contents.to_string();
99 dir.entry(name, BytesFile::new_node(contents.into_bytes()), mode!(IFREG, 0o444));
100 }
101 });
102 dir.subdir("cpufreq", 0o755, |dir| {
103 for domain in cpu_domains.iter() {
104 let min_core_id = domain.core_ids.iter().min().expect("core_ids is empty");
105 let name = format!("policy{}", min_core_id);
106 dir.subdir(&name, 0o755, |dir| build_cpufreq_directory(dir, domain));
107 }
108 });
109 dir.subdir("soc", 0o755, |dir| {
110 dir.subdir("0", 0o755, |dir| {
111 dir.entry(
112 "machine",
113 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
114 mode!(IFREG, 0o444),
115 );
116 });
117 });
118}
119
120fn get_cpu_domains() -> Vec<Arc<CpuFreqDomain>> {
129 if let Ok(domain_controller) = connect_to_protocol_sync::<fcpu::DomainControllerMarker>() {
131 if let Ok(mut domains) = domain_controller.list_domains(zx::MonotonicInstant::INFINITE) {
132 domains
134 .retain(|d| d.id.is_some() && d.core_ids.as_ref().map_or(false, |c| !c.is_empty()));
135 if !domains.is_empty() {
136 return domains
137 .iter()
138 .map(|domain| Arc::new(CpuFreqDomain::new(domain, Some(&domain_controller))))
139 .collect();
140 }
141 }
142 }
143
144 log_warn!(
145 "Could not retrieve CPU domains from fuchsia.power.cpu.DomainController, using CPU control devices instead."
146 );
147
148 if let Ok(proxies) = connect_to_cpu_devices() {
150 let mut domains = Vec::new();
151
152 for proxy in proxies {
153 let cpu_count = match proxy.get_num_logical_cores(zx::MonotonicInstant::INFINITE) {
154 Ok(count) => count,
155 Err(e) => {
156 log_warn!("get_num_logical_cores returned error: {}", e);
157 continue;
158 }
159 };
160 let domain_id = match proxy.get_domain_id(zx::MonotonicInstant::INFINITE) {
161 Ok(id) => id as u64,
162 Err(e) => {
163 log_warn!("get_domain_id returned error: {}", e);
164 continue;
165 }
166 };
167
168 let mut core_ids = Vec::with_capacity(cpu_count as usize);
169 let mut get_core_failed = false;
170 for i in 0..cpu_count {
171 let core_id = match proxy.get_logical_core_id(i, zx::MonotonicInstant::INFINITE) {
172 Ok(id) => id,
173 Err(e) => {
174 log_warn!("get_logical_core_id error in domain {}: {}", domain_id, e);
175 get_core_failed = true;
176 break;
177 }
178 };
179 core_ids.push(core_id);
180 }
181 if get_core_failed || core_ids.is_empty() {
182 log_warn!("get_logical_core_id failed in domain {}, skipping", domain_id);
183 continue;
184 }
185
186 let available_frequencies_hz =
187 match proxy.get_operating_point_count(zx::MonotonicInstant::INFINITE) {
188 Ok(Ok(count)) => {
189 let mut freqs = Vec::with_capacity(count as usize);
190 for i in 0..count {
191 if let Ok(Ok(info)) =
192 proxy.get_operating_point_info(i, zx::MonotonicInstant::INFINITE)
193 {
194 if info.frequency_hz > 0 {
195 freqs.push(info.frequency_hz as u64);
196 }
197 }
198 }
199 freqs.sort();
200 freqs.dedup();
201 Some(freqs)
202 }
203 _ => None,
204 };
205
206 domains.push(fcpu::DomainInfo {
207 id: Some(domain_id),
208 core_ids: Some(core_ids),
209 available_frequencies_hz,
210 ..Default::default()
211 });
212 }
213
214 if !domains.is_empty() {
215 return domains
216 .iter()
217 .map(|domain| Arc::new(CpuFreqDomain::new(domain, None)))
218 .collect();
219 }
220 }
221
222 log_warn!(
223 "Could not connect to CPU control devices, using default domain info from kernel CPU count."
224 );
225
226 let cpu_count = zx::system_get_num_cpus();
228 vec![Arc::new(CpuFreqDomain::new(
229 &fcpu::DomainInfo {
230 id: Some(0),
231 core_ids: Some((0..cpu_count as u64).collect()),
232 available_frequencies_hz: None,
233 name: None,
234 ..Default::default()
235 },
236 None,
237 ))]
238}
239
240fn hz_to_khz(hz: u64) -> u64 {
241 hz / 1000
242}
243
244const SCALING_GOVERNOR: &str = "performance";
249
250fn format_freq_khz(khz: Option<u64>) -> String {
252 format!("{}\n", khz.map(|f| f.to_string()).unwrap_or_default())
253}
254
255fn build_cpu_directory(
256 dir: &SimpleDirectoryMutator,
257 core_id: u64,
258 domain: &Arc<CpuFreqDomain>,
259 c2_provider: Arc<CpuC2Provider>,
260) {
261 let cluster_id = domain.domain_id;
262
263 dir.entry(
264 "cpu_capacity",
265 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
266 mode!(IFREG, 0o444),
267 );
268 dir.subdir("cpuidle", 0o755, |dir| {
269 build_cpuidle_directory(dir, core_id, c2_provider);
270 });
271 dir.subdir("cpufreq", 0o755, |dir| {
272 build_cpufreq_directory(dir, domain);
273 });
274 dir.subdir("topology", 0o755, |dir| {
275 dir.entry(
276 "cluster_id",
277 BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
278 mode!(IFREG, 0o444),
279 );
280 dir.entry(
281 "physical_package_id",
282 BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
283 mode!(IFREG, 0o444),
284 );
285 });
286}
287
288fn build_cpuidle_directory(
289 dir: &SimpleDirectoryMutator,
290 core_id: u64,
291 c2_provider: Arc<CpuC2Provider>,
292) {
293 let state0_c2_provider = c2_provider.clone();
294 dir.subdir("state0", 0o755, |dir| {
295 dir.entry("name", BytesFile::new_node(b"C1\n".to_vec()), mode!(IFREG, 0o444));
296 dir.entry("desc", BytesFile::new_node(b"idle\n".to_vec()), mode!(IFREG, 0o444));
297 dir.entry(
298 "time",
299 BytesFile::new_node(CpuIdleStatFile::new_time(core_id, state0_c2_provider)),
300 mode!(IFREG, 0o444),
301 );
302 dir.entry(
303 "usage",
304 BytesFile::new_node(CpuIdleStatFile::new_usage(core_id)),
305 mode!(IFREG, 0o444),
306 );
307 });
308 dir.subdir("state1", 0o755, |dir| {
309 dir.entry("name", BytesFile::new_node(b"C2\n".to_vec()), mode!(IFREG, 0o444));
310 dir.entry("desc", BytesFile::new_node(b"deep idle\n".to_vec()), mode!(IFREG, 0o444));
311 dir.entry(
312 "time",
313 BytesFile::new_node(CpuIdleC2StatFile {
314 core_id,
315 stat_type: CpuIdleStatType::Time,
316 provider: c2_provider.clone(),
317 }),
318 mode!(IFREG, 0o444),
319 );
320 dir.entry(
321 "usage",
322 BytesFile::new_node(CpuIdleC2StatFile {
323 core_id,
324 stat_type: CpuIdleStatType::Usage,
325 provider: c2_provider,
326 }),
327 mode!(IFREG, 0o444),
328 );
329 });
330}
331
332const SOFT_MAX_FREQ_VOTER: &str = "soft_max_freq";
333
334const VOTE_MANAGER_MAX_FREQ_NODES: &[&str] =
336 &["powerhint_max_freq", "thermal_max_freq", SOFT_MAX_FREQ_VOTER, "debug_max_freq"];
337
338const VOTE_MANAGER_MIN_FREQ_NODES: &[&str] =
340 &["powerhint_min_freq", "soft_min_freq", "debug_min_freq"];
341
342fn add_max_freq_entry(
343 dir: &SimpleDirectoryMutator,
344 name: &'static str,
345 domain: &Arc<CpuFreqDomain>,
346) {
347 if domain.is_tunable() {
350 dir.entry(name, ScalingMaxFreqFile::new_node(domain, name), mode!(IFREG, 0o644));
351 } else {
352 dir.entry(
353 name,
354 InertFile::new_node(format_freq_khz(domain.highest_khz())),
355 mode!(IFREG, 0o644),
356 );
357 }
358}
359
360fn add_min_freq_entry(
361 dir: &SimpleDirectoryMutator,
362 name: &'static str,
363 domain: &Arc<CpuFreqDomain>,
364) {
365 if domain.is_tunable() {
368 dir.entry(name, ScalingMinFreqFile::new_node(domain, name), mode!(IFREG, 0o644));
369 } else {
370 dir.entry(
371 name,
372 InertFile::new_node(format_freq_khz(domain.lowest_khz())),
373 mode!(IFREG, 0o644),
374 );
375 }
376}
377
378fn build_cpufreq_directory(dir: &SimpleDirectoryMutator, domain: &Arc<CpuFreqDomain>) {
379 dir.subdir("stats", 0o755, |dir| {
380 dir.entry("reset", CpuFreqStatsResetFile::new_node(), mode!(IFREG, 0o200));
381 dir.entry(
382 "time_in_state",
383 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
384 mode!(IFREG, 0o444),
385 );
386 });
387
388 dir.subdir("vote_manager", 0o755, |dir| {
392 for name in VOTE_MANAGER_MAX_FREQ_NODES {
393 add_max_freq_entry(dir, name, domain);
394 }
395 for name in VOTE_MANAGER_MIN_FREQ_NODES {
396 add_min_freq_entry(dir, name, domain);
397 }
398 });
399
400 let related_cpus_str = format!("{}\n", domain.core_ids.iter().sorted().join(" "));
401 dir.entry(
402 "related_cpus",
403 BytesFile::new_node(related_cpus_str.into_bytes()),
404 mode!(IFREG, 0o444),
405 );
406 dir.entry(
407 "scaling_cur_freq",
408 create_scaling_cur_freq_file(domain.domain_id),
409 mode!(IFREG, 0o444),
410 );
411
412 add_min_freq_entry(dir, "scaling_min_freq", domain);
413 add_max_freq_entry(dir, "scaling_max_freq", domain);
414
415 dir.entry(
416 "scaling_available_frequencies",
417 BytesFile::new_node((domain.sorted_khz.iter().join(" ") + "\n").into_bytes()),
418 mode!(IFREG, 0o444),
419 );
420 dir.entry(
421 "scaling_available_governors",
422 BytesFile::new_node(format!("{SCALING_GOVERNOR}\n").into_bytes()),
423 mode!(IFREG, 0o444),
424 );
425 dir.entry(
426 "scaling_governor",
427 InertFile::new_node(format!("{SCALING_GOVERNOR}\n")),
428 mode!(IFREG, 0o644),
429 );
430 dir.entry(
431 "cpuinfo_min_freq",
432 BytesFile::new_node(format_freq_khz(domain.lowest_khz()).into_bytes()),
433 mode!(IFREG, 0o444),
434 );
435 dir.entry(
436 "cpuinfo_max_freq",
437 BytesFile::new_node(format_freq_khz(domain.highest_khz()).into_bytes()),
438 mode!(IFREG, 0o444),
439 );
440}
441
442const VULNERABILITIES: &[(&str, &str)] = &[
443 ("gather_data_sampling", "Not affected\n"),
444 ("itlb_multihit", "Not affected\n"),
445 ("l1tf", "Not affected\n"),
446 ("mds", "Not affected\n"),
447 ("meltdown", "Not affected\n"),
448 ("mmio_stale_data", "Not affected\n"),
449 ("retbleed", "Not affected\n"),
450 ("spec_rstack_overflow", "Not affected\n"),
451 ("spec_store_bypass", "Not affected\n"),
452 ("spectre_v1", "Not affected\n"),
453 ("spectre_v2", "Not affected\n"),
454 ("srbds", "Not affected\n"),
455 ("tsx_async_abort", "Not affected\n"),
456];
457
458struct CpuFreqStatsResetFile {}
459
460impl CpuFreqStatsResetFile {
461 pub fn new_node() -> impl FsNodeOps {
462 BytesFile::new_node(Self {})
463 }
464}
465
466impl BytesFileOps for CpuFreqStatsResetFile {
467 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
469 Ok(())
470 }
471}
472
473#[derive(Default)]
474struct CpuFreqDomainState {
475 votes: HashMap<&'static str, (u64, fbroker::LeaseToken)>,
479
480 min_votes: HashMap<&'static str, (u64, fbroker::LeaseToken)>,
484
485 domain_controller: Option<fcpu::DomainControllerSynchronousProxy>,
488}
489
490fn is_lease_open(token: &fbroker::LeaseToken) -> bool {
491 token.wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST).is_err()
492}
493
494impl CpuFreqDomainState {
495 fn from_domain_controller(
499 domain_id: u64,
500 opps_hz: &[u64],
501 domain_controller: &fcpu::DomainControllerSynchronousProxy,
502 ) -> Self {
503 let max_hz = opps_hz.iter().max().copied();
504 match domain_controller.get_max_frequency(domain_id, zx::MonotonicInstant::INFINITE) {
505 Ok(Ok(opp_index)) => match opps_hz.get(opp_index as usize).copied() {
506 Some(hz) if Some(hz) == max_hz => Self::default(),
507 Some(_) => {
508 let lease_name = format!("starnix:{SOFT_MAX_FREQ_VOTER}");
509 match domain_controller.lease_max_frequency(
510 domain_id,
511 opp_index,
512 &lease_name,
513 zx::MonotonicInstant::INFINITE,
514 ) {
515 Ok(Ok(token)) => {
516 match domain_controller
517 .clear_max_frequency(domain_id, zx::MonotonicInstant::INFINITE)
518 {
519 Ok(Ok(())) => {}
520 other => log_warn!(
521 "Failed to clear initial max frequency for domain {}: {:?}",
522 domain_id,
523 other
524 ),
525 }
526 let mut votes = HashMap::new();
527 votes.insert(SOFT_MAX_FREQ_VOTER, (opp_index, token));
528 Self { votes, ..Self::default() }
529 }
530 other => {
531 log_warn!(
532 "Failed to lease initial soft_max_freq for domain {}: {:?}",
533 domain_id,
534 other
535 );
536 Self::default()
537 }
538 }
539 }
540 None => {
541 log_warn!(
542 "Initial max frequency index {} out of range for domain {} (len {})",
543 opp_index,
544 domain_id,
545 opps_hz.len()
546 );
547 Self::default()
548 }
549 },
550 other => {
551 log_warn!(
552 "Failed to query initial max frequency for domain {}: {:?}",
553 domain_id,
554 other
555 );
556 Self::default()
557 }
558 }
559 }
560}
561
562pub struct CpuFreqDomain {
565 domain_id: u64,
566
567 name: Option<String>,
569
570 core_ids: Vec<u64>,
572
573 opps_hz: Vec<u64>,
581
582 sorted_khz: Vec<u64>,
584
585 has_domain_controller: bool,
588
589 state: LockDepMutex<CpuFreqDomainState, CpuFreqProxyCacheLock>,
590}
591
592impl CpuFreqDomain {
593 fn new(
594 domain: &fcpu::DomainInfo,
595 domain_controller: Option<&fcpu::DomainControllerSynchronousProxy>,
596 ) -> Self {
597 let domain_id = domain.id.expect("id not available");
598 let opps_hz = domain.available_frequencies_hz.clone().unwrap_or_default();
599 let sorted_khz = opps_hz.iter().map(|hz| hz_to_khz(*hz)).sorted().dedup().collect();
600 let state = match domain_controller {
601 Some(dc) if !opps_hz.is_empty() => {
602 CpuFreqDomainState::from_domain_controller(domain_id, &opps_hz, dc)
603 }
604 _ => CpuFreqDomainState::default(),
605 };
606 Self {
607 domain_id,
608 name: domain.name.clone(),
609 core_ids: domain.core_ids.clone().expect("core_ids not available"),
610 opps_hz,
611 sorted_khz,
612 has_domain_controller: domain_controller.is_some(),
613 state: LockDepMutex::new(state),
614 }
615 }
616
617 pub fn name(&self) -> Option<&str> {
618 self.name.as_deref()
619 }
620
621 pub fn opp_count(&self) -> usize {
622 self.opps_hz.len()
623 }
624
625 pub fn get_vote(&self, voter: &'static str) -> Option<u64> {
626 self.state
627 .lock()
628 .votes
629 .get(voter)
630 .and_then(|(index, token)| is_lease_open(token).then_some(*index))
631 }
632
633 fn voter_frequency_khz(&self, voter: &'static str) -> Option<u64> {
636 self.get_vote(voter)
637 .and_then(|index| self.opps_hz.get(index as usize))
638 .map(|&hz| hz_to_khz(hz))
639 .or_else(|| self.highest_khz())
640 }
641
642 fn get_min_vote(&self, voter: &'static str) -> Option<u64> {
643 self.state
644 .lock()
645 .min_votes
646 .get(voter)
647 .and_then(|(index, token)| is_lease_open(token).then_some(*index))
648 }
649
650 fn min_voter_frequency_khz(&self, voter: &'static str) -> Option<u64> {
653 self.get_min_vote(voter)
654 .and_then(|index| self.opps_hz.get(index as usize))
655 .map(|&hz| hz_to_khz(hz))
656 .or_else(|| self.lowest_khz())
657 }
658
659 pub fn is_tunable(&self) -> bool {
662 self.has_domain_controller && !self.opps_hz.is_empty()
663 }
664
665 fn lowest_khz(&self) -> Option<u64> {
666 self.sorted_khz.first().copied()
667 }
668
669 fn highest_khz(&self) -> Option<u64> {
670 self.sorted_khz.last().copied()
671 }
672
673 fn opp_index_for_khz(&self, khz: u64) -> Option<u64> {
682 if khz >= self.highest_khz()? {
683 return None;
684 }
685 self.opps_hz
686 .iter()
687 .enumerate()
688 .filter(|(_, hz)| hz_to_khz(**hz) <= khz)
689 .max_by_key(|(_, hz)| **hz)
690 .or_else(|| self.opps_hz.iter().enumerate().min_by_key(|(_, hz)| **hz))
691 .map(|(index, _)| index as u64)
692 }
693
694 fn opp_index_for_min_khz(&self, khz: u64) -> Option<u64> {
703 if khz <= self.lowest_khz()? {
704 return None;
705 }
706 self.opps_hz
707 .iter()
708 .enumerate()
709 .filter(|(_, hz)| hz_to_khz(**hz) >= khz)
710 .min_by_key(|(_, hz)| **hz)
711 .or_else(|| self.opps_hz.iter().enumerate().max_by_key(|(_, hz)| **hz))
712 .map(|(index, _)| index as u64)
713 }
714
715 pub fn update_vote(&self, voter: &'static str, opp_index: Option<u64>) -> Result<(), Errno> {
718 self.update_lease_vote(voter, opp_index, |s| &mut s.votes, Self::lease_max_frequency)
719 }
720
721 fn update_min_vote(&self, voter: &'static str, opp_index: Option<u64>) -> Result<(), Errno> {
724 self.update_lease_vote(voter, opp_index, |s| &mut s.min_votes, Self::lease_min_frequency)
725 }
726
727 fn update_lease_vote(
728 &self,
729 voter: &'static str,
730 opp_index: Option<u64>,
731 votes_mut: impl Fn(
732 &mut CpuFreqDomainState,
733 ) -> &mut HashMap<&'static str, (u64, fbroker::LeaseToken)>,
734 acquire_lease: impl FnOnce(
735 &Self,
736 &mut CpuFreqDomainState,
737 u64,
738 &str,
739 ) -> Result<fbroker::LeaseToken, Errno>,
740 ) -> Result<(), Errno> {
741 if let Some(index) = opp_index {
742 if index as usize >= self.opps_hz.len() {
743 return error!(EINVAL);
744 }
745 }
746 let mut state = self.state.lock();
747 match opp_index {
748 Some(index) => {
749 if let Some((existing_index, existing_token)) = votes_mut(&mut state).get(voter) {
750 if *existing_index == index && is_lease_open(existing_token) {
751 return Ok(());
752 }
753 }
754 let lease_name = format!("starnix:{voter}");
755 let token = acquire_lease(self, &mut state, index, &lease_name)?;
756 votes_mut(&mut state).insert(voter, (index, token));
757 }
758 None => {
759 votes_mut(&mut state).remove(voter);
760 }
761 }
762 Ok(())
763 }
764
765 fn with_domain_controller<T, E: std::fmt::Debug>(
770 &self,
771 state: &mut CpuFreqDomainState,
772 call: impl FnOnce(&fcpu::DomainControllerSynchronousProxy) -> Result<T, E>,
773 ) -> Result<T, Errno> {
774 if state.domain_controller.is_none() {
775 state.domain_controller =
776 Some(connect_to_protocol_sync::<fcpu::DomainControllerMarker>().map_err(|e| {
777 log_warn!("Failed to connect to DomainController: {:?}", e);
778 errno!(ENODEV)
779 })?);
780 }
781 match call(state.domain_controller.as_ref().expect("connection was just established")) {
782 Ok(value) => Ok(value),
783 Err(e) => {
784 log_warn!("DomainController call failed for domain {}: {:?}", self.domain_id, e);
785 state.domain_controller = None;
786 error!(EIO)
787 }
788 }
789 }
790
791 fn max_frequency_khz(&self) -> Result<u64, Errno> {
793 let mut state = self.state.lock();
794 let index = self
795 .with_domain_controller(&mut state, |dc| {
796 dc.get_max_frequency(self.domain_id, zx::MonotonicInstant::INFINITE)
797 })?
798 .map_err(|e| {
799 log_warn!("get_max_frequency failed for domain {}: {:?}", self.domain_id, e);
800 errno!(EINVAL)
801 })?;
802 let hz = self.opps_hz.get(index as usize).ok_or_else(|| errno!(EINVAL))?;
803 Ok(hz_to_khz(*hz))
804 }
805
806 fn lease_max_frequency(
807 &self,
808 state: &mut CpuFreqDomainState,
809 index: u64,
810 lease_name: &str,
811 ) -> Result<fbroker::LeaseToken, Errno> {
812 self.with_domain_controller(state, |dc| {
813 dc.lease_max_frequency(
814 self.domain_id,
815 index,
816 lease_name,
817 zx::MonotonicInstant::INFINITE,
818 )
819 })?
820 .map_err(|e| {
821 log_warn!("lease_max_frequency failed for domain {}: {:?}", self.domain_id, e);
822 errno!(EINVAL)
823 })
824 }
825
826 fn min_frequency_khz(&self) -> Result<u64, Errno> {
828 let mut state = self.state.lock();
829 let index = self
830 .with_domain_controller(&mut state, |dc| {
831 dc.get_min_frequency(self.domain_id, zx::MonotonicInstant::INFINITE)
832 })?
833 .map_err(|e| {
834 log_warn!("get_min_frequency failed for domain {}: {:?}", self.domain_id, e);
835 errno!(EINVAL)
836 })?;
837 let hz = self.opps_hz.get(index as usize).ok_or_else(|| errno!(EINVAL))?;
838 Ok(hz_to_khz(*hz))
839 }
840
841 fn lease_min_frequency(
842 &self,
843 state: &mut CpuFreqDomainState,
844 index: u64,
845 lease_name: &str,
846 ) -> Result<fbroker::LeaseToken, Errno> {
847 self.with_domain_controller(state, |dc| {
848 dc.lease_min_frequency(
849 self.domain_id,
850 index,
851 lease_name,
852 zx::MonotonicInstant::INFINITE,
853 )
854 })?
855 .map_err(|e| {
856 log_warn!("lease_min_frequency failed for domain {}: {:?}", self.domain_id, e);
857 errno!(EINVAL)
858 })
859 }
860}
861
862struct ScalingMaxFreqFile {
868 domain: Arc<CpuFreqDomain>,
869 voter: &'static str,
870}
871
872impl ScalingMaxFreqFile {
873 fn new_node(domain: &Arc<CpuFreqDomain>, voter: &'static str) -> impl FsNodeOps {
874 BytesFile::new_node(Self { domain: domain.clone(), voter })
875 }
876}
877
878impl BytesFileOps for ScalingMaxFreqFile {
879 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
880 let content = if self.voter == "scaling_max_freq" {
881 format!("{}\n", self.domain.max_frequency_khz()?)
882 } else {
883 format_freq_khz(self.domain.voter_frequency_khz(self.voter))
884 };
885 Ok(content.into_bytes().into())
886 }
887
888 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
889 let opp_index = self.domain.opp_index_for_khz(parse_unsigned_file::<u64>(&data)?);
890 self.domain.update_vote(self.voter, opp_index)
891 }
892}
893
894struct ScalingMinFreqFile {
900 domain: Arc<CpuFreqDomain>,
901 voter: &'static str,
902}
903
904impl ScalingMinFreqFile {
905 fn new_node(domain: &Arc<CpuFreqDomain>, voter: &'static str) -> impl FsNodeOps {
906 BytesFile::new_node(Self { domain: domain.clone(), voter })
907 }
908}
909
910impl BytesFileOps for ScalingMinFreqFile {
911 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
912 let content = if self.voter == "scaling_min_freq" {
913 format!("{}\n", self.domain.min_frequency_khz()?)
914 } else {
915 format_freq_khz(self.domain.min_voter_frequency_khz(self.voter))
916 };
917 Ok(content.into_bytes().into())
918 }
919
920 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
921 let opp_index = self.domain.opp_index_for_min_khz(parse_unsigned_file::<u64>(&data)?);
922 self.domain.update_min_vote(self.voter, opp_index)
923 }
924}
925
926struct InertFile(Vec<u8>);
934
935impl InertFile {
936 fn new_node(contents: String) -> impl FsNodeOps {
937 BytesFile::new_node(Self(contents.into_bytes()))
938 }
939}
940
941impl BytesFileOps for InertFile {
942 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
943 Ok(())
944 }
945
946 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
947 Ok(self.0.as_slice().into())
948 }
949}
950
951const CPU_DIRECTORY: &str = "/svc/fuchsia.hardware.cpu.ctrl.Service";
952
953fn connect_to_cpu_devices() -> Result<Vec<fcpuctrl::DeviceSynchronousProxy>, Errno> {
954 let dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
955
956 let proxies: Vec<_> = dir
957 .filter_map(|r| r.ok())
958 .filter_map(|entry| {
959 let path = entry.path().join("device").into_os_string().into_string().ok()?;
960 let (client, server) = zx::Channel::create();
961 fdio::service_connect(&path, server).ok()?;
962 Some(fcpuctrl::DeviceSynchronousProxy::new(client))
963 })
964 .collect();
965
966 if proxies.is_empty() { error!(ENOENT) } else { Ok(proxies) }
967}
968
969fn connect_to_cpu_device_by_domain_id(
970 domain_id: u64,
971) -> Result<fcpuctrl::DeviceSynchronousProxy, Errno> {
972 let dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
973
974 dir.filter_map(|r| r.ok())
975 .find_map(|entry| {
976 let path = entry.path().join("device").into_os_string().into_string().ok()?;
977 let (client, server) = zx::Channel::create();
978 fdio::service_connect(&path, server).ok()?;
979 let proxy = fcpuctrl::DeviceSynchronousProxy::new(client);
980
981 let dev_domain_id = proxy.get_domain_id(zx::MonotonicInstant::INFINITE).ok()?;
982 if domain_id == dev_domain_id as u64 { Some(proxy) } else { None }
983 })
984 .ok_or_else(|| errno!(ENOENT))
985}
986
987fn create_scaling_cur_freq_file(domain_id: u64) -> impl FsNodeOps {
988 let proxy_cache =
989 LockDepMutex::<_, CpuFreqProxyCacheLock>::new(None::<fcpuctrl::DeviceSynchronousProxy>);
990 SimpleFileNode::new(move |_| {
991 let mut guard = proxy_cache.lock();
992 if guard.is_none() {
993 let proxy = connect_to_cpu_device_by_domain_id(domain_id)?;
994 *guard = Some(proxy);
995 }
996 let proxy = guard.as_ref().expect("must have a valid proxy");
997 let opp = match proxy.get_current_operating_point(zx::MonotonicInstant::INFINITE) {
998 Ok(opp) => opp,
999 Err(_) => {
1000 *guard = None;
1001 return error!(EINVAL);
1002 }
1003 };
1004 let info = match proxy.get_operating_point_info(opp, zx::MonotonicInstant::INFINITE) {
1005 Ok(info) => info,
1006 Err(_) => {
1007 *guard = None;
1008 return error!(EINVAL);
1009 }
1010 };
1011 let info = info.map_err(|e| from_status_like_fdio!(zx::Status::err_from_raw(e)))?;
1012 if info.frequency_hz <= 0 {
1013 return error!(EINVAL);
1014 }
1015 let freq_khz = hz_to_khz(info.frequency_hz as u64);
1016 Ok(BytesFile::new(format!("{}\n", freq_khz).into_bytes()))
1017 })
1018}
1019
1020enum CpuIdleStatType {
1021 Time,
1022 Usage,
1023}
1024
1025fn suspended_time() -> zx::BootDuration {
1032 let mono_ns = zx::MonotonicInstant::get().into_nanos();
1033 let boot_ns = zx::BootInstant::get().into_nanos();
1034 zx::BootDuration::from_nanos(boot_ns.saturating_sub(mono_ns))
1035}
1036
1037#[derive(Clone, Copy, Debug, Default, PartialEq)]
1040struct CoreC2Residency {
1041 id: i32,
1043 total_time: zx::BootDuration,
1045 entry_count: u64,
1047 awake_time: zx::MonotonicDuration,
1049}
1050
1051fn compute_c1_residency(
1062 total_idle: zx::MonotonicDuration,
1063 c2_residency: Option<CoreC2Residency>,
1064) -> zx::MonotonicDuration {
1065 let awake_c2 = c2_residency.map_or(zx::MonotonicDuration::ZERO, |c2| c2.awake_time);
1066 (total_idle - awake_c2).max(zx::MonotonicDuration::ZERO)
1067}
1068
1069#[derive(Clone, Copy, Default)]
1071struct CoreC2Accounting {
1072 last_suspended: zx::BootDuration,
1074 last_c2: zx::BootDuration,
1076 awake_c2: zx::MonotonicDuration,
1078}
1079
1080#[derive(Default)]
1093struct C2SuspendAccounting {
1094 cores: HashMap<i32, CoreC2Accounting>,
1096}
1097
1098impl C2SuspendAccounting {
1099 fn boot_duration_from_ms(ms: u64) -> zx::BootDuration {
1100 zx::BootDuration::from_millis(i64::try_from(ms).unwrap_or(i64::MAX))
1101 }
1102
1103 fn record(&mut self, suspended: zx::BootDuration, residencies: &[fpowerstats::StateResidency]) {
1107 let suspended = zx::BootDuration::from_millis(suspended.into_millis().max(0));
1109 for residency in residencies {
1110 let total_c2 = Self::boot_duration_from_ms(residency.total_time_ms);
1111 let core = self.cores.entry(residency.id).or_default();
1112 let suspended_delta = (suspended - core.last_suspended).max(zx::BootDuration::ZERO);
1113 let c2_delta = (total_c2 - core.last_c2).max(zx::BootDuration::ZERO);
1114 let awake_delta =
1115 zx::MonotonicDuration::from_nanos((c2_delta - suspended_delta).into_nanos().max(0));
1116 core.awake_c2 += awake_delta;
1117 core.last_suspended = core.last_suspended.max(suspended);
1118 core.last_c2 = core.last_c2.max(total_c2);
1119 }
1120 }
1121
1122 fn awake_c2(&self, core_id: i32) -> zx::MonotonicDuration {
1124 self.cores.get(&core_id).map_or(zx::MonotonicDuration::ZERO, |core| core.awake_c2)
1125 }
1126
1127 fn snapshot(&self, residencies: Vec<fpowerstats::StateResidency>) -> Vec<CoreC2Residency> {
1129 residencies
1130 .into_iter()
1131 .map(|state| CoreC2Residency {
1132 id: state.id,
1133 total_time: Self::boot_duration_from_ms(state.total_time_ms),
1134 entry_count: u64::from(state.entry_count),
1135 awake_time: self.awake_c2(state.id),
1136 })
1137 .collect()
1138 }
1139}
1140
1141enum CpuIdleC1Stat {
1143 Time {
1144 c2_provider: Arc<CpuC2Provider>,
1145 last_time_us: AtomicU64,
1152 },
1153 Usage,
1154}
1155
1156struct CpuIdleStatFile {
1160 core_id: u64,
1161 stat: CpuIdleC1Stat,
1162}
1163
1164impl CpuIdleStatFile {
1165 fn new_time(core_id: u64, c2_provider: Arc<CpuC2Provider>) -> Self {
1166 Self { core_id, stat: CpuIdleC1Stat::Time { c2_provider, last_time_us: AtomicU64::new(0) } }
1167 }
1168
1169 fn new_usage(core_id: u64) -> Self {
1170 Self { core_id, stat: CpuIdleC1Stat::Usage }
1171 }
1172
1173 fn clamp_c1_residency_us(last_time_us: &AtomicU64, c1: zx::MonotonicDuration) -> u64 {
1174 let c1_us = u64::try_from(c1.into_micros()).unwrap_or(0);
1175 last_time_us.fetch_max(c1_us, Ordering::Relaxed).max(c1_us)
1176 }
1177}
1178
1179impl BytesFileOps for CpuIdleStatFile {
1180 fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1181 let cpu_stats = current_task
1182 .kernel()
1183 .stats
1184 .get()
1185 .get_cpu_stats(zx::MonotonicInstant::INFINITE)
1186 .map_err(|_| errno!(EINVAL))?;
1187 let per_cpu_stats = cpu_stats.per_cpu_stats.ok_or_else(|| errno!(EINVAL))?;
1188 let per_cpu = per_cpu_stats.get(self.core_id as usize).ok_or_else(|| errno!(EINVAL))?;
1189 let value = match &self.stat {
1190 CpuIdleC1Stat::Time { c2_provider, last_time_us } => {
1191 let total_idle = zx::MonotonicDuration::from_nanos(
1192 per_cpu.idle_time.ok_or_else(|| errno!(EINVAL))?,
1193 );
1194 let c2 = c2_provider.residency(self.core_id)?;
1195 let c1 = compute_c1_residency(total_idle, c2);
1196 Self::clamp_c1_residency_us(last_time_us, c1)
1197 }
1198 CpuIdleC1Stat::Usage => per_cpu.reschedules.ok_or_else(|| errno!(EINVAL))?,
1199 };
1200 Ok(format!("{value}\n").into_bytes().into())
1201 }
1202}
1203
1204const POWER_STATS_SERVICE_DIRECTORY: &str = "/svc/fuchsia.hardware.power.stats.Service";
1205const CPU_C2_ENTITY_NAME: &str = "cpu_c2";
1206
1207const POWER_STATS_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
1209
1210const CLOCK_READ_JITTER_TOLERANCE: zx::BootDuration = zx::BootDuration::from_millis(1);
1213
1214#[derive(Default)]
1217enum ProviderState {
1218 #[default]
1220 Unconnected,
1221 Connected(Arc<fpowerstats::DeviceSynchronousProxy>),
1223 Disconnected,
1226 Unavailable,
1229}
1230
1231#[derive(Default)]
1256struct CpuC2Provider {
1257 state: LockDepMutex<ProviderState, CpuC2ProxyCacheLock>,
1258 suspend_accounting: LockDepMutex<C2SuspendAccounting, CpuC2SuspendAccountingLock>,
1259}
1260
1261impl CpuC2Provider {
1262 fn connect() -> Option<fpowerstats::DeviceSynchronousProxy> {
1263 let dir = std::fs::read_dir(POWER_STATS_SERVICE_DIRECTORY).ok()?;
1264 dir.filter_map(|entry| entry.ok()).find_map(|entry| {
1265 let path = entry.path().join("device").into_os_string().into_string().ok()?;
1266 let (client, server) = zx::Channel::create();
1267 fdio::service_connect(&path, server).ok()?;
1268 let proxy = fpowerstats::DeviceSynchronousProxy::new(client);
1269 let deadline = zx::MonotonicInstant::after(POWER_STATS_FIDL_TIMEOUT);
1270 let entities = proxy.get_entities(deadline).ok()?;
1271 entities.iter().any(|entity| entity.name == CPU_C2_ENTITY_NAME).then_some(proxy)
1272 })
1273 }
1274
1275 fn proxy(&self) -> Result<Option<Arc<fpowerstats::DeviceSynchronousProxy>>, Errno> {
1276 if matches!(*self.state.lock(), ProviderState::Unconnected | ProviderState::Disconnected) {
1277 let connected_proxy = Self::connect().map(Arc::new);
1278 let mut state = self.state.lock();
1279 match (&*state, connected_proxy) {
1280 (ProviderState::Unconnected | ProviderState::Disconnected, Some(proxy)) => {
1281 *state = ProviderState::Connected(proxy);
1282 }
1283 (ProviderState::Unconnected, None) => {
1284 *state = ProviderState::Unavailable;
1285 }
1286 (ProviderState::Disconnected, None)
1287 | (ProviderState::Connected(_) | ProviderState::Unavailable, _) => {}
1288 }
1289 }
1290 match &*self.state.lock() {
1291 ProviderState::Connected(proxy) => Ok(Some(proxy.clone())),
1292 ProviderState::Unavailable | ProviderState::Unconnected => Ok(None),
1293 ProviderState::Disconnected => error!(EIO),
1294 }
1295 }
1296
1297 fn record_sample(
1300 &self,
1301 suspended_before: zx::BootDuration,
1302 suspended_after: zx::BootDuration,
1303 residencies: Vec<fpowerstats::StateResidency>,
1304 ) -> Vec<CoreC2Residency> {
1305 let mut accounting = self.suspend_accounting.lock();
1306 if suspended_after - suspended_before <= CLOCK_READ_JITTER_TOLERANCE {
1310 accounting.record(suspended_after, &residencies);
1311 }
1312 accounting.snapshot(residencies)
1313 }
1314
1315 fn sample(
1318 &self,
1319 proxy: &Arc<fpowerstats::DeviceSynchronousProxy>,
1320 ) -> Result<Vec<CoreC2Residency>, Errno> {
1321 let suspended_before = suspended_time();
1322 let deadline = zx::MonotonicInstant::after(POWER_STATS_FIDL_TIMEOUT);
1323 let reports = match proxy.get_stats(deadline) {
1324 Ok(reports) => reports,
1325 Err(e) => {
1326 log_warn!("Failed to read power stats for {CPU_C2_ENTITY_NAME}: {e}");
1327 let mut state = self.state.lock();
1328 if matches!(
1329 &*state,
1330 ProviderState::Connected(current) if Arc::ptr_eq(current, proxy)
1331 ) {
1332 *state = ProviderState::Disconnected;
1333 }
1334 return error!(EIO);
1335 }
1336 };
1337 let suspended_after = suspended_time();
1338 let residencies = reports
1339 .into_iter()
1340 .find(|stats| stats.entity == CPU_C2_ENTITY_NAME)
1341 .map_or_else(Vec::new, |stats| stats.state_residency_data);
1342 Ok(self.record_sample(suspended_before, suspended_after, residencies))
1343 }
1344
1345 fn sample_all(&self) {
1347 if let Ok(Some(proxy)) = self.proxy() {
1348 let _ = self.sample(&proxy);
1349 }
1350 }
1351
1352 fn residency(&self, core_id: u64) -> Result<Option<CoreC2Residency>, Errno> {
1353 let core_id = i32::try_from(core_id).map_err(|_| errno!(EINVAL))?;
1354 let Some(proxy) = self.proxy()? else {
1355 return Ok(None);
1356 };
1357 Ok(self.sample(&proxy)?.into_iter().find(|s| s.id == core_id))
1358 }
1359}
1360
1361pub fn sample_cpu_c2_residency(kernel: &Kernel) {
1367 kernel.expando.get::<CpuC2Provider>().sample_all();
1368}
1369
1370struct CpuIdleC2StatFile {
1374 core_id: u64,
1375 stat_type: CpuIdleStatType,
1376 provider: Arc<CpuC2Provider>,
1377}
1378
1379impl CpuIdleC2StatFile {
1380 fn read_value(&self) -> Result<u64, Errno> {
1381 let state = self.provider.residency(self.core_id)?.ok_or_else(|| errno!(ENODEV))?;
1382 Ok(match self.stat_type {
1383 CpuIdleStatType::Time => u64::try_from(state.total_time.into_micros()).unwrap_or(0),
1384 CpuIdleStatType::Usage => state.entry_count,
1385 })
1386 }
1387}
1388
1389impl BytesFileOps for CpuIdleC2StatFile {
1390 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1391 let value = self.read_value()?;
1392 Ok(format!("{value}\n").into_bytes().into())
1393 }
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398 use super::*;
1399 use fidl::endpoints::RequestStream;
1400 use futures::TryStreamExt;
1401
1402 #[::fuchsia::test]
1403 fn cpu_c2_provider_reads_firmware_residencies() {
1404 let (client, server) = zx::Channel::create();
1405 let server_thread = std::thread::spawn(move || {
1406 let mut executor = fuchsia_async::LocalExecutor::default();
1407 executor.run_singlethreaded(async move {
1408 let mut stream = fpowerstats::DeviceRequestStream::from_channel(
1409 fuchsia_async::Channel::from_channel(server),
1410 );
1411 if let Some(fpowerstats::DeviceRequest::GetStats { responder }) =
1412 stream.try_next().await.expect("failed to read request")
1413 {
1414 let reports = vec![fpowerstats::Stats {
1415 entity: CPU_C2_ENTITY_NAME.to_string(),
1416 state_residency_data: vec![
1417 fpowerstats::StateResidency {
1418 id: 0,
1419 name: "C2_CPU0".to_string(),
1420 total_time_ms: 34,
1421 entry_count: 12,
1422 last_entry_timestamp_ms: 0,
1423 },
1424 fpowerstats::StateResidency {
1425 id: 1,
1426 name: "C2_CPU1".to_string(),
1427 total_time_ms: 78,
1428 entry_count: 56,
1429 last_entry_timestamp_ms: 0,
1430 },
1431 ],
1432 }];
1433 responder.send(&reports).expect("failed to send response");
1434 }
1435 });
1436 });
1437
1438 let provider = CpuC2Provider {
1439 state: LockDepMutex::new(ProviderState::Connected(Arc::new(
1440 fpowerstats::DeviceSynchronousProxy::new(client),
1441 ))),
1442 suspend_accounting: Default::default(),
1443 };
1444 let core1 = provider
1445 .residency(1)
1446 .expect("residency call must succeed")
1447 .expect("core 1 residency must be present");
1448 assert_eq!(core1.entry_count, 56);
1449 assert_eq!(core1.total_time, zx::BootDuration::from_millis(78));
1450
1451 server_thread.join().expect("server thread panicked");
1452
1453 assert_eq!(provider.residency(0), error!(EIO));
1457 assert!(matches!(*provider.state.lock(), ProviderState::Disconnected));
1458 assert_eq!(provider.residency(0), error!(EIO));
1459 assert!(matches!(*provider.state.lock(), ProviderState::Disconnected));
1460
1461 let unconfigured = Arc::new(CpuC2Provider::default());
1465 assert_eq!(unconfigured.residency(0), Ok(None));
1466 assert!(matches!(*unconfigured.state.lock(), ProviderState::Unavailable));
1467 let c2_file = CpuIdleC2StatFile {
1468 core_id: 0,
1469 stat_type: CpuIdleStatType::Time,
1470 provider: unconfigured,
1471 };
1472 assert_eq!(c2_file.read_value(), error!(ENODEV));
1473 }
1474
1475 fn c2_residency(id: i32, total_time_ms: u64) -> fpowerstats::StateResidency {
1476 fpowerstats::StateResidency {
1477 id,
1478 name: format!("C2_CPU{id}"),
1479 total_time_ms,
1480 entry_count: 1,
1481 last_entry_timestamp_ms: 0,
1482 }
1483 }
1484
1485 #[::fuchsia::test]
1486 fn c1_time_subtracts_awake_c2_residency_and_saturates_at_zero() {
1487 let c2 = CoreC2Residency {
1488 id: 0,
1489 total_time: zx::BootDuration::from_millis(1_899),
1490 entry_count: 1,
1491 awake_time: zx::MonotonicDuration::from_millis(1_899),
1492 };
1493
1494 assert_eq!(
1496 compute_c1_residency(zx::MonotonicDuration::from_seconds(5), Some(c2)),
1497 zx::MonotonicDuration::from_micros(3_101_000)
1498 );
1499
1500 assert_eq!(
1502 compute_c1_residency(zx::MonotonicDuration::from_seconds(5), None),
1503 zx::MonotonicDuration::from_seconds(5)
1504 );
1505
1506 assert_eq!(
1508 compute_c1_residency(zx::MonotonicDuration::from_seconds(1), Some(c2)),
1509 zx::MonotonicDuration::ZERO
1510 );
1511
1512 let c2 = CoreC2Residency {
1514 id: 0,
1515 total_time: zx::BootDuration::from_millis(154_160),
1516 entry_count: 2,
1517 awake_time: zx::MonotonicDuration::from_millis(1_860),
1518 };
1519 assert_eq!(
1520 compute_c1_residency(zx::MonotonicDuration::from_millis(252_370), Some(c2)),
1521 zx::MonotonicDuration::from_micros(250_510_000)
1522 );
1523 }
1524
1525 #[::fuchsia::test]
1526 fn c2_suspend_accounting_attributes_suspend_per_core() {
1527 let mut accounting = C2SuspendAccounting::default();
1528
1529 accounting.record(
1531 zx::BootDuration::from_millis(0),
1532 &[c2_residency(0, 10_000), c2_residency(1, 2_000)],
1533 );
1534 assert_eq!(accounting.awake_c2(0), zx::MonotonicDuration::from_millis(10_000));
1535 assert_eq!(accounting.awake_c2(1), zx::MonotonicDuration::from_millis(2_000));
1536
1537 accounting.record(
1540 zx::BootDuration::from_millis(152_000),
1541 &[c2_residency(0, 10_200), c2_residency(1, 154_500)],
1542 );
1543 assert_eq!(accounting.awake_c2(0), zx::MonotonicDuration::from_millis(10_000));
1544 assert_eq!(accounting.awake_c2(1), zx::MonotonicDuration::from_millis(2_500));
1545
1546 accounting.record(
1550 zx::BootDuration::from_millis(152_000),
1551 &[c2_residency(0, 70_000), c2_residency(1, 160_000), c2_residency(2, 153_000)],
1552 );
1553 assert_eq!(accounting.awake_c2(0), zx::MonotonicDuration::from_millis(69_800));
1554 assert_eq!(accounting.awake_c2(1), zx::MonotonicDuration::from_millis(8_000));
1555 assert_eq!(accounting.awake_c2(2), zx::MonotonicDuration::from_millis(1_000));
1556
1557 accounting.record(
1559 zx::BootDuration::from_millis(100_000),
1560 &[c2_residency(0, 10_000), c2_residency(1, 100_000)],
1561 );
1562 assert_eq!(accounting.awake_c2(0), zx::MonotonicDuration::from_millis(69_800));
1563 assert_eq!(accounting.awake_c2(1), zx::MonotonicDuration::from_millis(8_000));
1564
1565 assert_eq!(accounting.awake_c2(7), zx::MonotonicDuration::ZERO);
1567 }
1568
1569 #[::fuchsia::test]
1570 fn record_sample_discards_sample_that_raced_suspend() {
1571 let provider = CpuC2Provider::default();
1572
1573 let snapshot = provider.record_sample(
1575 zx::BootDuration::from_millis(0),
1576 zx::BootDuration::from_millis(0),
1577 vec![c2_residency(0, 10_000)],
1578 );
1579 assert_eq!(
1580 snapshot,
1581 vec![CoreC2Residency {
1582 id: 0,
1583 total_time: zx::BootDuration::from_millis(10_000),
1584 entry_count: 1,
1585 awake_time: zx::MonotonicDuration::from_millis(10_000),
1586 }]
1587 );
1588
1589 let raced = provider.record_sample(
1593 zx::BootDuration::from_millis(0),
1594 zx::BootDuration::from_millis(50_000),
1595 vec![c2_residency(0, 60_000)],
1596 );
1597 assert_eq!(
1598 raced,
1599 vec![CoreC2Residency {
1600 id: 0,
1601 total_time: zx::BootDuration::from_millis(60_000),
1602 entry_count: 1,
1603 awake_time: zx::MonotonicDuration::from_millis(10_000),
1604 }]
1605 );
1606
1607 let after = provider.record_sample(
1611 zx::BootDuration::from_millis(49_999),
1612 zx::BootDuration::from_millis(50_000),
1613 vec![c2_residency(0, 60_500)],
1614 );
1615 assert_eq!(
1616 after,
1617 vec![CoreC2Residency {
1618 id: 0,
1619 total_time: zx::BootDuration::from_millis(60_500),
1620 entry_count: 1,
1621 awake_time: zx::MonotonicDuration::from_millis(10_500),
1622 }]
1623 );
1624 }
1625
1626 #[::fuchsia::test]
1627 fn c1_residency_clamps_to_high_water_mark() {
1628 let last_time_us = AtomicU64::new(0);
1629 assert_eq!(
1630 CpuIdleStatFile::clamp_c1_residency_us(
1631 &last_time_us,
1632 zx::MonotonicDuration::from_micros(3_101_000),
1633 ),
1634 3_101_000
1635 );
1636 assert_eq!(
1638 CpuIdleStatFile::clamp_c1_residency_us(
1639 &last_time_us,
1640 zx::MonotonicDuration::from_micros(3_100_500),
1641 ),
1642 3_101_000
1643 );
1644 assert_eq!(
1646 CpuIdleStatFile::clamp_c1_residency_us(
1647 &last_time_us,
1648 zx::MonotonicDuration::from_micros(3_200_000),
1649 ),
1650 3_200_000
1651 );
1652 }
1653
1654 #[::fuchsia::test]
1655 fn min_freq_votes_manage_domain_controller_leases() {
1656 let (client, server) = zx::Channel::create();
1657 let (lease_tx, lease_rx) = std::sync::mpsc::channel();
1658 let (prev_token_tx, prev_token_rx) = std::sync::mpsc::channel::<zx::EventPair>();
1659 let server_thread = std::thread::spawn(move || {
1660 let mut executor = fuchsia_async::LocalExecutor::default();
1661 executor.run_singlethreaded(async move {
1662 let mut stream = fcpu::DomainControllerRequestStream::from_channel(
1663 fuchsia_async::Channel::from_channel(server),
1664 );
1665 while let Some(request) =
1666 stream.try_next().await.expect("failed to read DomainController request")
1667 {
1668 match request {
1669 fcpu::DomainControllerRequest::GetMaxFrequency { domain_id, responder } => {
1670 assert_eq!(domain_id, 0);
1671 responder.send(Ok(2)).expect("failed to send max frequency");
1672 }
1673 fcpu::DomainControllerRequest::LeaseMinFrequency {
1674 domain_id,
1675 frequency_index,
1676 lease_name,
1677 responder,
1678 } => {
1679 assert_eq!(domain_id, 0);
1680 if let Ok(prev_token) = prev_token_rx.try_recv() {
1681 assert!(is_lease_open(&prev_token));
1682 }
1683 let (client_token, server_token) = zx::EventPair::create();
1684 responder.send(Ok(client_token)).expect("failed to send lease token");
1685 lease_tx
1686 .send((lease_name, frequency_index, server_token))
1687 .expect("failed to forward lease token");
1688 }
1689 other => panic!("unexpected request: {other:?}"),
1690 }
1691 }
1692 });
1693 });
1694
1695 let proxy = fcpu::DomainControllerSynchronousProxy::new(client);
1696 let domain = CpuFreqDomain::new(
1697 &fcpu::DomainInfo {
1698 id: Some(0),
1699 core_ids: Some(vec![0, 1]),
1700 available_frequencies_hz: Some(vec![820_000_000, 1_200_000_000, 1_632_000_000]),
1701 name: Some("cluster0".to_string()),
1702 ..Default::default()
1703 },
1704 Some(&proxy),
1705 );
1706 domain.state.lock().domain_controller = Some(proxy);
1707
1708 assert_eq!(
1710 domain.update_min_vote("powerhint_min_freq", domain.opp_index_for_min_khz(1_632_000)),
1711 Ok(())
1712 );
1713 assert_eq!(domain.min_voter_frequency_khz("powerhint_min_freq"), Some(1_632_000));
1714 assert_eq!(domain.min_voter_frequency_khz("soft_min_freq"), Some(820_000));
1715 let (name1, opp1, token1) = lease_rx.recv().expect("expected first lease token");
1716 assert_eq!(name1, "starnix:powerhint_min_freq");
1717 assert_eq!(opp1, 2);
1718 assert!(is_lease_open(&token1));
1719
1720 assert_eq!(
1723 domain.update_min_vote("powerhint_min_freq", domain.opp_index_for_min_khz(1_632_000)),
1724 Ok(())
1725 );
1726 assert!(lease_rx.try_recv().is_err());
1727 assert!(is_lease_open(&token1));
1728
1729 assert_eq!(
1731 domain.update_min_vote("soft_min_freq", domain.opp_index_for_min_khz(1_200_000)),
1732 Ok(())
1733 );
1734 assert_eq!(domain.min_voter_frequency_khz("soft_min_freq"), Some(1_200_000));
1735 let (name2, opp2, token2) = lease_rx.recv().expect("expected second lease token");
1736 assert_eq!(name2, "starnix:soft_min_freq");
1737 assert_eq!(opp2, 1);
1738 assert!(is_lease_open(&token1));
1739 assert!(is_lease_open(&token2));
1740
1741 prev_token_tx
1744 .send(token1.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate token1"))
1745 .expect("send previous token");
1746 assert_eq!(
1747 domain.update_min_vote("powerhint_min_freq", domain.opp_index_for_min_khz(1_200_000)),
1748 Ok(())
1749 );
1750 assert_eq!(domain.min_voter_frequency_khz("powerhint_min_freq"), Some(1_200_000));
1751 let (name3, opp3, token3) = lease_rx.recv().expect("expected replacement lease token");
1752 assert_eq!(name3, "starnix:powerhint_min_freq");
1753 assert_eq!(opp3, 1);
1754 assert_eq!(
1755 token1.wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE),
1756 zx::WaitResult::Ok(zx::Signals::EVENTPAIR_PEER_CLOSED)
1757 );
1758 assert!(is_lease_open(&token3));
1759 assert!(is_lease_open(&token2));
1760
1761 drop(token3);
1764 assert_eq!(domain.get_min_vote("powerhint_min_freq"), None);
1765 assert_eq!(domain.min_voter_frequency_khz("powerhint_min_freq"), Some(820_000));
1766 assert_eq!(
1767 domain.update_min_vote("powerhint_min_freq", domain.opp_index_for_min_khz(1_200_000)),
1768 Ok(())
1769 );
1770 assert_eq!(domain.min_voter_frequency_khz("powerhint_min_freq"), Some(1_200_000));
1771 let (name4, opp4, token4) = lease_rx.recv().expect("expected re-leased token");
1772 assert_eq!(name4, "starnix:powerhint_min_freq");
1773 assert_eq!(opp4, 1);
1774 assert!(is_lease_open(&token4));
1775
1776 assert_eq!(
1778 domain.update_min_vote("powerhint_min_freq", domain.opp_index_for_min_khz(820_000)),
1779 Ok(())
1780 );
1781 assert_eq!(domain.min_voter_frequency_khz("powerhint_min_freq"), Some(820_000));
1782 assert_eq!(
1783 token4.wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE),
1784 zx::WaitResult::Ok(zx::Signals::EVENTPAIR_PEER_CLOSED)
1785 );
1786 assert!(is_lease_open(&token2));
1787
1788 assert_eq!(
1790 domain.update_min_vote("soft_min_freq", domain.opp_index_for_min_khz(0)),
1791 Ok(())
1792 );
1793 assert_eq!(domain.min_voter_frequency_khz("soft_min_freq"), Some(820_000));
1794 assert_eq!(
1795 token2.wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE),
1796 zx::WaitResult::Ok(zx::Signals::EVENTPAIR_PEER_CLOSED)
1797 );
1798
1799 drop(domain);
1800 server_thread.join().expect("server thread panicked");
1801 }
1802}