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_cpu as fcpu;
16use fidl_fuchsia_power_cpu_manager as fcpumanager;
17use fuchsia_component::client::connect_to_protocol_sync;
18use itertools::Itertools;
19use starnix_logging::{bug_ref, log_warn};
20use starnix_sync::{CpuC2ProxyCacheLock, CpuFreqProxyCacheLock, LockDepMutex};
21use starnix_uapi::errors::Errno;
22use starnix_uapi::file_mode::mode;
23use starnix_uapi::{errno, error, from_status_like_fdio};
24use std::borrow::Cow;
25use std::collections::HashMap;
26use std::sync::Arc;
27use zx;
28
29#[derive(Clone)]
30struct CpuFreqDomains(Arc<[Arc<CpuFreqDomain>]>);
31
32pub fn get_cpu_freq_domains(kernel: &Kernel) -> Arc<[Arc<CpuFreqDomain>]> {
35 kernel.expando.get_or_init(|| CpuFreqDomains(get_cpu_domains().into())).0.clone()
36}
37
38pub fn register_cpu_devices(kernel: &Kernel) {
40 let cpu_domains = get_cpu_freq_domains(kernel);
43
44 let mut core_to_domain_map: Vec<(u64, &Arc<CpuFreqDomain>)> = cpu_domains
45 .iter()
46 .flat_map(|domain| domain.core_ids.iter().map(move |id| (*id, domain)))
47 .collect();
48 core_to_domain_map.sort_by_key(|(id, _)| *id);
49 core_to_domain_map.dedup_by_key(|(id, _)| *id);
50
51 let core_count = core_to_domain_map.len();
52 let registry = &kernel.device_registry;
53 let system_device = registry.objects.system_device();
54 let cpu_root =
55 registry.add_subsystemless_device("cpu".into(), Some(system_device), |device, dir| {
56 build_device_directory(device, dir);
57 build_cpu_subsystem_directory(dir, &cpu_domains, core_count);
58 });
59
60 let cpu_bus = registry.objects.get_or_create_bus("cpu".into());
61 let c2_provider = Arc::new(CpuC2Provider::default());
62 for (core_id, domain) in &core_to_domain_map {
63 let name: FsString = format!("cpu{}", core_id).into();
64 let c2_provider = c2_provider.clone();
65 registry.add_bus_device(
66 name.as_ref(),
67 Some(cpu_root.clone()),
68 cpu_bus.clone(),
69 |device, dir| {
70 build_device_directory(device, dir);
71 build_cpu_directory(dir, *core_id, domain, c2_provider);
72 },
73 );
74 }
75}
76
77fn build_cpu_subsystem_directory(
79 dir: &SimpleDirectoryMutator,
80 cpu_domains: &[Arc<CpuFreqDomain>],
81 core_count: usize,
82) {
83 dir.entry(
84 "online",
85 BytesFile::new_node(format!("0-{}\n", core_count.saturating_sub(1)).into_bytes()),
86 mode!(IFREG, 0o444),
87 );
88 dir.entry(
89 "possible",
90 BytesFile::new_node(format!("0-{}\n", core_count.saturating_sub(1)).into_bytes()),
91 mode!(IFREG, 0o444),
92 );
93 dir.subdir("vulnerabilities", 0o755, |dir| {
94 for (name, contents) in VULNERABILITIES {
95 let contents = contents.to_string();
96 dir.entry(name, BytesFile::new_node(contents.into_bytes()), mode!(IFREG, 0o444));
97 }
98 });
99 dir.subdir("cpufreq", 0o755, |dir| {
100 for domain in cpu_domains.iter() {
101 let min_core_id = domain.core_ids.iter().min().expect("core_ids is empty");
102 let name = format!("policy{}", min_core_id);
103 dir.subdir(&name, 0o755, |dir| build_cpufreq_directory(dir, domain));
104 }
105 });
106 dir.subdir("soc", 0o755, |dir| {
107 dir.subdir("0", 0o755, |dir| {
108 dir.entry(
109 "machine",
110 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
111 mode!(IFREG, 0o444),
112 );
113 });
114 });
115}
116
117fn get_cpu_domains() -> Vec<Arc<CpuFreqDomain>> {
126 if let Ok(domain_controller) = connect_to_protocol_sync::<fcpu::DomainControllerMarker>() {
128 if let Ok(mut domains) = domain_controller.list_domains(zx::MonotonicInstant::INFINITE) {
129 domains
131 .retain(|d| d.id.is_some() && d.core_ids.as_ref().map_or(false, |c| !c.is_empty()));
132 if !domains.is_empty() {
133 return domains
134 .iter()
135 .map(|domain| Arc::new(CpuFreqDomain::new(domain, Some(&domain_controller))))
136 .collect();
137 }
138 }
139 }
140
141 log_warn!(
142 "Could not retrieve CPU domains from fuchsia.power.cpu.DomainController, using CPU control devices instead."
143 );
144
145 if let Ok(proxies) = connect_to_cpu_devices() {
147 let mut domains = Vec::new();
148
149 for proxy in proxies {
150 let cpu_count = match proxy.get_num_logical_cores(zx::MonotonicInstant::INFINITE) {
151 Ok(count) => count,
152 Err(e) => {
153 log_warn!("get_num_logical_cores returned error: {}", e);
154 continue;
155 }
156 };
157 let domain_id = match proxy.get_domain_id(zx::MonotonicInstant::INFINITE) {
158 Ok(id) => id as u64,
159 Err(e) => {
160 log_warn!("get_domain_id returned error: {}", e);
161 continue;
162 }
163 };
164
165 let mut core_ids = Vec::with_capacity(cpu_count as usize);
166 let mut get_core_failed = false;
167 for i in 0..cpu_count {
168 let core_id = match proxy.get_logical_core_id(i, zx::MonotonicInstant::INFINITE) {
169 Ok(id) => id,
170 Err(e) => {
171 log_warn!("get_logical_core_id error in domain {}: {}", domain_id, e);
172 get_core_failed = true;
173 break;
174 }
175 };
176 core_ids.push(core_id);
177 }
178 if get_core_failed || core_ids.is_empty() {
179 log_warn!("get_logical_core_id failed in domain {}, skipping", domain_id);
180 continue;
181 }
182
183 let available_frequencies_hz =
184 match proxy.get_operating_point_count(zx::MonotonicInstant::INFINITE) {
185 Ok(Ok(count)) => {
186 let mut freqs = Vec::with_capacity(count as usize);
187 for i in 0..count {
188 if let Ok(Ok(info)) =
189 proxy.get_operating_point_info(i, zx::MonotonicInstant::INFINITE)
190 {
191 if info.frequency_hz > 0 {
192 freqs.push(info.frequency_hz as u64);
193 }
194 }
195 }
196 freqs.sort();
197 freqs.dedup();
198 Some(freqs)
199 }
200 _ => None,
201 };
202
203 domains.push(fcpu::DomainInfo {
204 id: Some(domain_id),
205 core_ids: Some(core_ids),
206 available_frequencies_hz,
207 ..Default::default()
208 });
209 }
210
211 if !domains.is_empty() {
212 return domains
213 .iter()
214 .map(|domain| Arc::new(CpuFreqDomain::new(domain, None)))
215 .collect();
216 }
217 }
218
219 log_warn!(
220 "Could not connect to CPU control devices, using default domain info from kernel CPU count."
221 );
222
223 let cpu_count = zx::system_get_num_cpus();
225 vec![Arc::new(CpuFreqDomain::new(
226 &fcpu::DomainInfo {
227 id: Some(0),
228 core_ids: Some((0..cpu_count as u64).collect()),
229 available_frequencies_hz: None,
230 name: None,
231 ..Default::default()
232 },
233 None,
234 ))]
235}
236
237fn hz_to_khz(hz: u64) -> u64 {
238 hz / 1000
239}
240
241const SCALING_GOVERNOR: &str = "performance";
246
247fn format_freq_khz(khz: Option<u64>) -> String {
249 format!("{}\n", khz.map(|f| f.to_string()).unwrap_or_default())
250}
251
252fn build_cpu_directory(
253 dir: &SimpleDirectoryMutator,
254 core_id: u64,
255 domain: &Arc<CpuFreqDomain>,
256 c2_provider: Arc<CpuC2Provider>,
257) {
258 let cluster_id = domain.domain_id;
259
260 dir.entry(
261 "cpu_capacity",
262 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
263 mode!(IFREG, 0o444),
264 );
265 dir.subdir("cpuidle", 0o755, |dir| {
266 build_cpuidle_directory(dir, core_id, c2_provider);
267 });
268 dir.subdir("cpufreq", 0o755, |dir| {
269 build_cpufreq_directory(dir, domain);
270 });
271 dir.subdir("topology", 0o755, |dir| {
272 dir.entry(
273 "cluster_id",
274 BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
275 mode!(IFREG, 0o444),
276 );
277 dir.entry(
278 "physical_package_id",
279 BytesFile::new_node(format!("{cluster_id}\n").into_bytes()),
280 mode!(IFREG, 0o444),
281 );
282 });
283}
284
285fn build_cpuidle_directory(
286 dir: &SimpleDirectoryMutator,
287 core_id: u64,
288 c2_provider: Arc<CpuC2Provider>,
289) {
290 let state0_c2_provider = c2_provider.clone();
291 dir.subdir("state0", 0o755, |dir| {
292 dir.entry("name", BytesFile::new_node(b"C1\n".to_vec()), mode!(IFREG, 0o444));
293 dir.entry("desc", BytesFile::new_node(b"idle\n".to_vec()), mode!(IFREG, 0o444));
294 dir.entry(
295 "time",
296 BytesFile::new_node(CpuIdleStatFile {
297 core_id,
298 stat_type: CpuIdleStatType::Time,
299 c2_provider: state0_c2_provider.clone(),
300 }),
301 mode!(IFREG, 0o444),
302 );
303 dir.entry(
304 "usage",
305 BytesFile::new_node(CpuIdleStatFile {
306 core_id,
307 stat_type: CpuIdleStatType::Usage,
308 c2_provider: state0_c2_provider,
309 }),
310 mode!(IFREG, 0o444),
311 );
312 });
313 dir.subdir("state1", 0o755, |dir| {
314 dir.entry("name", BytesFile::new_node(b"C2\n".to_vec()), mode!(IFREG, 0o444));
315 dir.entry("desc", BytesFile::new_node(b"deep idle\n".to_vec()), mode!(IFREG, 0o444));
316 dir.entry(
317 "time",
318 BytesFile::new_node(CpuIdleC2StatFile {
319 core_id,
320 stat_type: CpuIdleStatType::Time,
321 provider: c2_provider.clone(),
322 }),
323 mode!(IFREG, 0o444),
324 );
325 dir.entry(
326 "usage",
327 BytesFile::new_node(CpuIdleC2StatFile {
328 core_id,
329 stat_type: CpuIdleStatType::Usage,
330 provider: c2_provider,
331 }),
332 mode!(IFREG, 0o444),
333 );
334 });
335}
336
337const SOFT_MAX_FREQ_VOTER: &str = "soft_max_freq";
338
339const VOTE_MANAGER_MAX_FREQ_NODES: &[&str] =
341 &["powerhint_max_freq", "thermal_max_freq", SOFT_MAX_FREQ_VOTER, "debug_max_freq"];
342
343const VOTE_MANAGER_MIN_FREQ_NODES: &[&str] =
345 &["powerhint_min_freq", "soft_min_freq", "debug_min_freq"];
346
347fn add_max_freq_entry(
348 dir: &SimpleDirectoryMutator,
349 name: &'static str,
350 domain: &Arc<CpuFreqDomain>,
351) {
352 if domain.is_tunable() {
355 dir.entry(name, ScalingMaxFreqFile::new_node(domain, name), mode!(IFREG, 0o644));
356 } else {
357 dir.entry(
358 name,
359 InertFile::new_node(format_freq_khz(domain.highest_khz())),
360 mode!(IFREG, 0o644),
361 );
362 }
363}
364
365fn add_min_freq_entry(
366 dir: &SimpleDirectoryMutator,
367 name: &'static str,
368 domain: &Arc<CpuFreqDomain>,
369) {
370 if domain.is_tunable() {
373 dir.entry(name, ScalingMinFreqFile::new_node(domain, name), mode!(IFREG, 0o644));
374 } else {
375 dir.entry(
376 name,
377 InertFile::new_node(format_freq_khz(domain.lowest_khz())),
378 mode!(IFREG, 0o644),
379 );
380 }
381}
382
383fn build_cpufreq_directory(dir: &SimpleDirectoryMutator, domain: &Arc<CpuFreqDomain>) {
384 dir.subdir("stats", 0o755, |dir| {
385 dir.entry("reset", CpuFreqStatsResetFile::new_node(), mode!(IFREG, 0o200));
386 dir.entry(
387 "time_in_state",
388 StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
389 mode!(IFREG, 0o444),
390 );
391 });
392
393 dir.subdir("vote_manager", 0o755, |dir| {
397 for name in VOTE_MANAGER_MAX_FREQ_NODES {
398 add_max_freq_entry(dir, name, domain);
399 }
400 for name in VOTE_MANAGER_MIN_FREQ_NODES {
401 add_min_freq_entry(dir, name, domain);
402 }
403 });
404
405 let related_cpus_str = format!("{}\n", domain.core_ids.iter().sorted().join(" "));
406 dir.entry(
407 "related_cpus",
408 BytesFile::new_node(related_cpus_str.into_bytes()),
409 mode!(IFREG, 0o444),
410 );
411 dir.entry(
412 "scaling_cur_freq",
413 create_scaling_cur_freq_file(domain.domain_id),
414 mode!(IFREG, 0o444),
415 );
416
417 add_min_freq_entry(dir, "scaling_min_freq", domain);
418
419 add_max_freq_entry(dir, "scaling_max_freq", domain);
420
421 dir.entry(
422 "scaling_available_frequencies",
423 BytesFile::new_node((domain.sorted_khz.iter().join(" ") + "\n").into_bytes()),
424 mode!(IFREG, 0o444),
425 );
426 dir.entry(
427 "scaling_available_governors",
428 BytesFile::new_node(format!("{SCALING_GOVERNOR}\n").into_bytes()),
429 mode!(IFREG, 0o444),
430 );
431 dir.entry(
432 "scaling_governor",
433 InertFile::new_node(format!("{SCALING_GOVERNOR}\n")),
434 mode!(IFREG, 0o644),
435 );
436 dir.entry(
437 "cpuinfo_min_freq",
438 BytesFile::new_node(format_freq_khz(domain.lowest_khz()).into_bytes()),
439 mode!(IFREG, 0o444),
440 );
441 dir.entry(
442 "cpuinfo_max_freq",
443 BytesFile::new_node(format_freq_khz(domain.highest_khz()).into_bytes()),
444 mode!(IFREG, 0o444),
445 );
446}
447
448const VULNERABILITIES: &[(&str, &str)] = &[
449 ("gather_data_sampling", "Not affected\n"),
450 ("itlb_multihit", "Not affected\n"),
451 ("l1tf", "Not affected\n"),
452 ("mds", "Not affected\n"),
453 ("meltdown", "Not affected\n"),
454 ("mmio_stale_data", "Not affected\n"),
455 ("retbleed", "Not affected\n"),
456 ("spec_rstack_overflow", "Not affected\n"),
457 ("spec_store_bypass", "Not affected\n"),
458 ("spectre_v1", "Not affected\n"),
459 ("spectre_v2", "Not affected\n"),
460 ("srbds", "Not affected\n"),
461 ("tsx_async_abort", "Not affected\n"),
462];
463
464struct CpuFreqStatsResetFile {}
465
466impl CpuFreqStatsResetFile {
467 pub fn new_node() -> impl FsNodeOps {
468 BytesFile::new_node(Self {})
469 }
470}
471
472impl BytesFileOps for CpuFreqStatsResetFile {
473 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
475 Ok(())
476 }
477}
478
479#[derive(Default)]
480struct CpuFreqDomainState {
481 votes: HashMap<&'static str, u64>,
484
485 min_votes: HashMap<&'static str, u64>,
489
490 applied_opp: Option<Option<u64>>,
494
495 domain_controller: Option<fcpu::DomainControllerSynchronousProxy>,
498
499 boost_proxy: Option<fcpumanager::BoostSynchronousProxy>,
502
503 boost_token: Option<zx::EventPair>,
506}
507
508impl CpuFreqDomainState {
509 fn from_domain_controller(
513 domain_id: u64,
514 opps_hz: &[u64],
515 domain_controller: &fcpu::DomainControllerSynchronousProxy,
516 ) -> Self {
517 let max_hz = opps_hz.iter().max().copied();
518 match domain_controller.get_max_frequency(domain_id, zx::MonotonicInstant::INFINITE) {
519 Ok(Ok(opp_index)) => match opps_hz.get(opp_index as usize).copied() {
520 Some(hz) if Some(hz) == max_hz => {
521 Self { applied_opp: Some(None), ..Self::default() }
522 }
523 Some(_) => {
524 let mut votes = HashMap::new();
525 votes.insert(SOFT_MAX_FREQ_VOTER, opp_index);
526 Self { votes, applied_opp: Some(Some(opp_index)), ..Self::default() }
527 }
528 None => {
529 log_warn!(
530 "Initial max frequency index {} out of range for domain {} (len {})",
531 opp_index,
532 domain_id,
533 opps_hz.len()
534 );
535 Self::default()
536 }
537 },
538 other => {
539 log_warn!(
540 "Failed to query initial max frequency for domain {}: {:?}",
541 domain_id,
542 other
543 );
544 Self::default()
545 }
546 }
547 }
548}
549
550pub struct CpuFreqDomain {
553 domain_id: u64,
554
555 name: Option<String>,
557
558 core_ids: Vec<u64>,
560
561 opps_hz: Vec<u64>,
569
570 sorted_khz: Vec<u64>,
572
573 has_domain_controller: bool,
576
577 state: LockDepMutex<CpuFreqDomainState, CpuFreqProxyCacheLock>,
578}
579
580impl CpuFreqDomain {
581 fn new(
582 domain: &fcpu::DomainInfo,
583 domain_controller: Option<&fcpu::DomainControllerSynchronousProxy>,
584 ) -> Self {
585 let domain_id = domain.id.expect("id not available");
586 let opps_hz = domain.available_frequencies_hz.clone().unwrap_or_default();
587 let sorted_khz = opps_hz.iter().map(|hz| hz_to_khz(*hz)).sorted().dedup().collect();
588 let state = match domain_controller {
589 Some(dc) if !opps_hz.is_empty() => {
590 CpuFreqDomainState::from_domain_controller(domain_id, &opps_hz, dc)
591 }
592 _ => CpuFreqDomainState::default(),
593 };
594 Self {
595 domain_id,
596 name: domain.name.clone(),
597 core_ids: domain.core_ids.clone().expect("core_ids not available"),
598 opps_hz,
599 sorted_khz,
600 has_domain_controller: domain_controller.is_some(),
601 state: LockDepMutex::new(state),
602 }
603 }
604
605 pub fn name(&self) -> Option<&str> {
606 self.name.as_deref()
607 }
608
609 pub fn opp_count(&self) -> usize {
610 self.opps_hz.len()
611 }
612
613 pub fn get_vote(&self, voter: &'static str) -> Option<u64> {
614 self.state.lock().votes.get(voter).copied()
615 }
616
617 fn voter_frequency_khz(&self, voter: &'static str) -> Option<u64> {
620 self.get_vote(voter)
621 .and_then(|index| self.opps_hz.get(index as usize))
622 .map(|&hz| hz_to_khz(hz))
623 .or_else(|| self.highest_khz())
624 }
625
626 pub fn is_tunable(&self) -> bool {
629 self.has_domain_controller && !self.opps_hz.is_empty()
630 }
631
632 fn lowest_khz(&self) -> Option<u64> {
633 self.sorted_khz.first().copied()
634 }
635
636 fn highest_khz(&self) -> Option<u64> {
637 self.sorted_khz.last().copied()
638 }
639
640 fn opp_index_for_khz(&self, khz: u64) -> Option<u64> {
649 if khz >= self.highest_khz()? {
650 return None;
651 }
652 self.opps_hz
653 .iter()
654 .enumerate()
655 .filter(|(_, hz)| hz_to_khz(**hz) <= khz)
656 .max_by_key(|(_, hz)| **hz)
657 .or_else(|| self.opps_hz.iter().enumerate().min_by_key(|(_, hz)| **hz))
658 .map(|(index, _)| index as u64)
659 }
660
661 pub fn update_vote(&self, voter: &'static str, opp_index: Option<u64>) -> Result<(), Errno> {
664 if let Some(index) = opp_index {
665 if index as usize >= self.opps_hz.len() {
666 return error!(EINVAL);
667 }
668 }
669 let mut state = self.state.lock();
670 let previous = match opp_index {
671 Some(index) => state.votes.insert(voter, index),
672 None => state.votes.remove(voter),
673 };
674 let result = self.apply_effective_vote(&mut state);
675 if result.is_err() {
676 match previous {
677 Some(prev) => {
678 state.votes.insert(voter, prev);
679 }
680 None => {
681 state.votes.remove(voter);
682 }
683 }
684 }
685 result
686 }
687
688 fn apply_effective_vote(&self, state: &mut CpuFreqDomainState) -> Result<(), Errno> {
689 let effective_opp = state
690 .votes
691 .values()
692 .copied()
693 .min_by_key(|&index| (self.opps_hz[index as usize], std::cmp::Reverse(index)));
694 if state.applied_opp == Some(effective_opp) {
695 return Ok(());
696 }
697 state.applied_opp = None;
698 match effective_opp {
699 Some(index) => self.set_max_frequency(state, index)?,
700 None => self.clear_max_frequency(state)?,
701 }
702 state.applied_opp = Some(effective_opp);
703 Ok(())
704 }
705
706 fn with_domain_controller<T, E: std::fmt::Debug>(
711 &self,
712 state: &mut CpuFreqDomainState,
713 call: impl FnOnce(&fcpu::DomainControllerSynchronousProxy) -> Result<T, E>,
714 ) -> Result<T, Errno> {
715 if state.domain_controller.is_none() {
716 state.domain_controller =
717 Some(connect_to_protocol_sync::<fcpu::DomainControllerMarker>().map_err(|e| {
718 log_warn!("Failed to connect to DomainController: {:?}", e);
719 errno!(ENODEV)
720 })?);
721 }
722 match call(state.domain_controller.as_ref().expect("connection was just established")) {
723 Ok(value) => Ok(value),
724 Err(e) => {
725 log_warn!("DomainController call failed for domain {}: {:?}", self.domain_id, e);
726 state.domain_controller = None;
727 state.applied_opp = None;
728 error!(EIO)
729 }
730 }
731 }
732
733 fn max_frequency_khz(&self) -> Result<u64, Errno> {
735 let mut state = self.state.lock();
736 let index = self
737 .with_domain_controller(&mut state, |dc| {
738 dc.get_max_frequency(self.domain_id, zx::MonotonicInstant::INFINITE)
739 })?
740 .map_err(|e| {
741 log_warn!("get_max_frequency failed for domain {}: {:?}", self.domain_id, e);
742 errno!(EINVAL)
743 })?;
744 let hz = self.opps_hz.get(index as usize).ok_or_else(|| errno!(EINVAL))?;
745 Ok(hz_to_khz(*hz))
746 }
747
748 fn set_max_frequency(&self, state: &mut CpuFreqDomainState, index: u64) -> Result<(), Errno> {
749 self.with_domain_controller(state, |dc| {
750 dc.set_max_frequency(self.domain_id, index, zx::MonotonicInstant::INFINITE)
751 })?
752 .map_err(|e| {
753 log_warn!("set_max_frequency failed for domain {}: {:?}", self.domain_id, e);
754 errno!(EINVAL)
755 })
756 }
757
758 fn clear_max_frequency(&self, state: &mut CpuFreqDomainState) -> Result<(), Errno> {
759 self.with_domain_controller(state, |dc| {
760 dc.clear_max_frequency(self.domain_id, zx::MonotonicInstant::INFINITE)
761 })?
762 .map_err(|e| {
763 log_warn!("clear_max_frequency failed for domain {}: {:?}", self.domain_id, e);
764 errno!(EINVAL)
765 })
766 }
767
768 fn min_frequency_khz(&self, voter: &'static str) -> Option<u64> {
771 let state = self.state.lock();
772 let active_vote = if voter == "scaling_min_freq" {
773 state.min_votes.values().copied().max()
774 } else {
775 state.min_votes.get(voter).copied()
776 };
777 active_vote.or_else(|| self.lowest_khz())
778 }
779
780 pub fn update_min_vote(
783 &self,
784 kernel: Option<&Kernel>,
785 voter: &'static str,
786 khz: u64,
787 ) -> Result<(), Errno> {
788 let lowest_khz = self.lowest_khz().unwrap_or(0);
789 let highest_khz = self.highest_khz().unwrap_or(u64::MAX);
790 let mut state = self.state.lock();
791 if khz <= lowest_khz {
792 state.min_votes.remove(voter);
793 } else {
794 state.min_votes.insert(voter, khz.min(highest_khz));
795 }
796
797 if !state.min_votes.is_empty() && state.boost_token.is_none() {
798 if state.boost_proxy.is_none() {
799 state.boost_proxy = kernel
800 .and_then(|k| {
801 k.connect_to_protocol_at_container_svc::<fcpumanager::BoostMarker>()
802 .ok()
803 .map(|c| c.into_sync_proxy())
804 })
805 .or_else(|| connect_to_protocol_sync::<fcpumanager::BoostMarker>().ok());
806 }
807 if let Some(ref proxy) = state.boost_proxy {
808 match proxy.boost(zx::MonotonicInstant::INFINITE) {
809 Ok(Ok(token)) => {
810 state.boost_token = Some(token);
811 }
812 Ok(Err(e)) => {
813 log_warn!(
814 "cpu-manager Boost returned error for domain {}: {:?}",
815 self.domain_id,
816 e
817 );
818 }
819 Err(e) => {
820 log_warn!(
821 "cpu-manager Boost FIDL call failed for domain {}: {:?}",
822 self.domain_id,
823 e
824 );
825 state.boost_proxy = None;
826 }
827 }
828 }
829 } else if state.min_votes.is_empty() && state.boost_token.is_some() {
830 state.boost_token = None;
831 }
832 Ok(())
833 }
834}
835
836struct ScalingMaxFreqFile {
842 domain: Arc<CpuFreqDomain>,
843 voter: &'static str,
844}
845
846impl ScalingMaxFreqFile {
847 fn new_node(domain: &Arc<CpuFreqDomain>, voter: &'static str) -> impl FsNodeOps {
848 BytesFile::new_node(Self { domain: domain.clone(), voter })
849 }
850}
851
852impl BytesFileOps for ScalingMaxFreqFile {
853 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
854 let content = if self.voter == "scaling_max_freq" {
855 format!("{}\n", self.domain.max_frequency_khz()?)
856 } else {
857 format_freq_khz(self.domain.voter_frequency_khz(self.voter))
858 };
859 Ok(content.into_bytes().into())
860 }
861
862 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
863 let opp_index = self.domain.opp_index_for_khz(parse_unsigned_file::<u64>(&data)?);
864 self.domain.update_vote(self.voter, opp_index)
865 }
866}
867
868struct ScalingMinFreqFile {
875 domain: Arc<CpuFreqDomain>,
876 voter: &'static str,
877}
878
879impl ScalingMinFreqFile {
880 fn new_node(domain: &Arc<CpuFreqDomain>, voter: &'static str) -> impl FsNodeOps {
881 BytesFile::new_node(Self { domain: domain.clone(), voter })
882 }
883}
884
885impl BytesFileOps for ScalingMinFreqFile {
886 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
887 Ok(format_freq_khz(self.domain.min_frequency_khz(self.voter)).into_bytes().into())
888 }
889
890 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
891 let khz = parse_unsigned_file::<u64>(&data)?;
892 self.domain.update_min_vote(Some(current_task.kernel()), self.voter, khz)
893 }
894}
895
896struct InertFile(Vec<u8>);
904
905impl InertFile {
906 fn new_node(contents: String) -> impl FsNodeOps {
907 BytesFile::new_node(Self(contents.into_bytes()))
908 }
909}
910
911impl BytesFileOps for InertFile {
912 fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
913 Ok(())
914 }
915
916 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
917 Ok(self.0.as_slice().into())
918 }
919}
920
921const CPU_DIRECTORY: &str = "/svc/fuchsia.hardware.cpu.ctrl.Service";
922
923fn connect_to_cpu_devices() -> Result<Vec<fcpuctrl::DeviceSynchronousProxy>, Errno> {
924 let dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
925
926 let proxies: Vec<_> = dir
927 .filter_map(|r| r.ok())
928 .filter_map(|entry| {
929 let path = entry.path().join("device").into_os_string().into_string().ok()?;
930 let (client, server) = zx::Channel::create();
931 fdio::service_connect(&path, server).ok()?;
932 Some(fcpuctrl::DeviceSynchronousProxy::new(client))
933 })
934 .collect();
935
936 if proxies.is_empty() { error!(ENOENT) } else { Ok(proxies) }
937}
938
939fn connect_to_cpu_device_by_domain_id(
940 domain_id: u64,
941) -> Result<fcpuctrl::DeviceSynchronousProxy, Errno> {
942 let dir = std::fs::read_dir(CPU_DIRECTORY).map_err(|_| errno!(EINVAL))?;
943
944 dir.filter_map(|r| r.ok())
945 .find_map(|entry| {
946 let path = entry.path().join("device").into_os_string().into_string().ok()?;
947 let (client, server) = zx::Channel::create();
948 fdio::service_connect(&path, server).ok()?;
949 let proxy = fcpuctrl::DeviceSynchronousProxy::new(client);
950
951 let dev_domain_id = proxy.get_domain_id(zx::MonotonicInstant::INFINITE).ok()?;
952 if domain_id == dev_domain_id as u64 { Some(proxy) } else { None }
953 })
954 .ok_or_else(|| errno!(ENOENT))
955}
956
957fn create_scaling_cur_freq_file(domain_id: u64) -> impl FsNodeOps {
958 let proxy_cache =
959 LockDepMutex::<_, CpuFreqProxyCacheLock>::new(None::<fcpuctrl::DeviceSynchronousProxy>);
960 SimpleFileNode::new(move |_| {
961 let mut guard = proxy_cache.lock();
962 if guard.is_none() {
963 let proxy = connect_to_cpu_device_by_domain_id(domain_id)?;
964 *guard = Some(proxy);
965 }
966 let proxy = guard.as_ref().expect("must have a valid proxy");
967 let opp = match proxy.get_current_operating_point(zx::MonotonicInstant::INFINITE) {
968 Ok(opp) => opp,
969 Err(_) => {
970 *guard = None;
971 return error!(EINVAL);
972 }
973 };
974 let info = match proxy.get_operating_point_info(opp, zx::MonotonicInstant::INFINITE) {
975 Ok(info) => info,
976 Err(_) => {
977 *guard = None;
978 return error!(EINVAL);
979 }
980 };
981 let info = info.map_err(|e| from_status_like_fdio!(zx::Status::err_from_raw(e)))?;
982 if info.frequency_hz <= 0 {
983 return error!(EINVAL);
984 }
985 let freq_khz = hz_to_khz(info.frequency_hz as u64);
986 Ok(BytesFile::new(format!("{}\n", freq_khz).into_bytes()))
987 })
988}
989
990enum CpuIdleStatType {
991 Time,
992 Usage,
993}
994
995fn c1_time_us(zircon_idle_ns: i64, c2_residency: Option<&fpowerstats::StateResidency>) -> u64 {
998 let total_idle_us = u64::try_from(zircon_idle_ns).unwrap_or(0) / 1000;
999 let c2_time_us = c2_residency.map_or(0, |state| state.total_time_ms.saturating_mul(1000));
1000 total_idle_us.saturating_sub(c2_time_us)
1001}
1002
1003struct CpuIdleStatFile {
1007 core_id: u64,
1008 stat_type: CpuIdleStatType,
1009 c2_provider: Arc<CpuC2Provider>,
1010}
1011
1012impl BytesFileOps for CpuIdleStatFile {
1013 fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1014 let cpu_stats = current_task
1015 .kernel()
1016 .stats
1017 .get()
1018 .get_cpu_stats(zx::MonotonicInstant::INFINITE)
1019 .map_err(|_| errno!(EINVAL))?;
1020 let per_cpu_stats = cpu_stats.per_cpu_stats.ok_or_else(|| errno!(EINVAL))?;
1021 let per_cpu = per_cpu_stats.get(self.core_id as usize).ok_or_else(|| errno!(EINVAL))?;
1022 let value = match self.stat_type {
1023 CpuIdleStatType::Time => {
1024 let idle_ns = per_cpu.idle_time.ok_or_else(|| errno!(EINVAL))?;
1025 let c2 = self.c2_provider.residency(self.core_id)?;
1026 c1_time_us(idle_ns, c2.as_ref())
1027 }
1028 CpuIdleStatType::Usage => per_cpu.reschedules.ok_or_else(|| errno!(EINVAL))?,
1029 };
1030 Ok(format!("{value}\n").into_bytes().into())
1031 }
1032}
1033
1034const POWER_STATS_SERVICE_DIRECTORY: &str = "/svc/fuchsia.hardware.power.stats.Service";
1035const CPU_C2_ENTITY_NAME: &str = "cpu_c2";
1036
1037const POWER_STATS_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
1039
1040#[derive(Default)]
1043enum ProviderState {
1044 #[default]
1046 Unconnected,
1047 Connected(Arc<fpowerstats::DeviceSynchronousProxy>),
1049 Disconnected,
1052 Unavailable,
1055}
1056
1057#[derive(Default)]
1080struct CpuC2Provider {
1081 state: LockDepMutex<ProviderState, CpuC2ProxyCacheLock>,
1082}
1083
1084impl CpuC2Provider {
1085 fn connect() -> Option<fpowerstats::DeviceSynchronousProxy> {
1086 let dir = std::fs::read_dir(POWER_STATS_SERVICE_DIRECTORY).ok()?;
1087 dir.filter_map(|entry| entry.ok()).find_map(|entry| {
1088 let path = entry.path().join("device").into_os_string().into_string().ok()?;
1089 let (client, server) = zx::Channel::create();
1090 fdio::service_connect(&path, server).ok()?;
1091 let proxy = fpowerstats::DeviceSynchronousProxy::new(client);
1092 let deadline = zx::MonotonicInstant::after(POWER_STATS_FIDL_TIMEOUT);
1093 let entities = proxy.get_entities(deadline).ok()?;
1094 entities.iter().any(|entity| entity.name == CPU_C2_ENTITY_NAME).then_some(proxy)
1095 })
1096 }
1097
1098 fn proxy(&self) -> Result<Option<Arc<fpowerstats::DeviceSynchronousProxy>>, Errno> {
1099 if matches!(*self.state.lock(), ProviderState::Unconnected | ProviderState::Disconnected) {
1100 let connected_proxy = Self::connect().map(Arc::new);
1101 let mut state = self.state.lock();
1102 match (&*state, connected_proxy) {
1103 (ProviderState::Unconnected | ProviderState::Disconnected, Some(proxy)) => {
1104 *state = ProviderState::Connected(proxy);
1105 }
1106 (ProviderState::Unconnected, None) => {
1107 *state = ProviderState::Unavailable;
1108 }
1109 (ProviderState::Disconnected, None)
1110 | (ProviderState::Connected(_) | ProviderState::Unavailable, _) => {}
1111 }
1112 }
1113 match &*self.state.lock() {
1114 ProviderState::Connected(proxy) => Ok(Some(proxy.clone())),
1115 ProviderState::Unavailable | ProviderState::Unconnected => Ok(None),
1116 ProviderState::Disconnected => error!(EIO),
1117 }
1118 }
1119
1120 fn residency(&self, core_id: u64) -> Result<Option<fpowerstats::StateResidency>, Errno> {
1121 let core_id = i32::try_from(core_id).map_err(|_| errno!(EINVAL))?;
1122 let Some(proxy) = self.proxy()? else {
1123 return Ok(None);
1124 };
1125 let deadline = zx::MonotonicInstant::after(POWER_STATS_FIDL_TIMEOUT);
1126 let reports = match proxy.get_stats(deadline) {
1127 Ok(reports) => reports,
1128 Err(e) => {
1129 log_warn!("Failed to read power stats for {CPU_C2_ENTITY_NAME}: {e}");
1130 let mut state = self.state.lock();
1131 if matches!(
1132 &*state,
1133 ProviderState::Connected(current) if Arc::ptr_eq(current, &proxy)
1134 ) {
1135 *state = ProviderState::Disconnected;
1136 }
1137 return error!(EIO);
1138 }
1139 };
1140 Ok(reports
1141 .into_iter()
1142 .find(|stats| stats.entity == CPU_C2_ENTITY_NAME)
1143 .and_then(|stats| stats.state_residency_data.into_iter().find(|s| s.id == core_id)))
1144 }
1145}
1146
1147struct CpuIdleC2StatFile {
1151 core_id: u64,
1152 stat_type: CpuIdleStatType,
1153 provider: Arc<CpuC2Provider>,
1154}
1155
1156impl BytesFileOps for CpuIdleC2StatFile {
1157 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1158 let value =
1159 self.provider.residency(self.core_id)?.map_or(0, |state| match self.stat_type {
1160 CpuIdleStatType::Time => state.total_time_ms.saturating_mul(1000),
1161 CpuIdleStatType::Usage => u64::from(state.entry_count),
1162 });
1163 Ok(format!("{value}\n").into_bytes().into())
1164 }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use super::*;
1170 use fidl::endpoints::RequestStream;
1171 use futures::TryStreamExt;
1172
1173 #[::fuchsia::test]
1174 fn cpu_c2_provider_reads_firmware_residencies() {
1175 let (client, server) = zx::Channel::create();
1176 let server_thread = std::thread::spawn(move || {
1177 let mut executor = fuchsia_async::LocalExecutor::default();
1178 executor.run_singlethreaded(async move {
1179 let mut stream = fpowerstats::DeviceRequestStream::from_channel(
1180 fuchsia_async::Channel::from_channel(server),
1181 );
1182 if let Some(fpowerstats::DeviceRequest::GetStats { responder }) =
1183 stream.try_next().await.expect("failed to read request")
1184 {
1185 let reports = vec![fpowerstats::Stats {
1186 entity: CPU_C2_ENTITY_NAME.to_string(),
1187 state_residency_data: vec![
1188 fpowerstats::StateResidency {
1189 id: 0,
1190 name: "C2_CPU0".to_string(),
1191 total_time_ms: 34,
1192 entry_count: 12,
1193 last_entry_timestamp_ms: 0,
1194 },
1195 fpowerstats::StateResidency {
1196 id: 1,
1197 name: "C2_CPU1".to_string(),
1198 total_time_ms: 78,
1199 entry_count: 56,
1200 last_entry_timestamp_ms: 0,
1201 },
1202 ],
1203 }];
1204 responder.send(&reports).expect("failed to send response");
1205 }
1206 });
1207 });
1208
1209 let provider = CpuC2Provider {
1210 state: LockDepMutex::new(ProviderState::Connected(Arc::new(
1211 fpowerstats::DeviceSynchronousProxy::new(client),
1212 ))),
1213 };
1214 let core1 = provider
1215 .residency(1)
1216 .expect("residency call must succeed")
1217 .expect("core 1 residency must be present");
1218 assert_eq!(core1.entry_count, 56);
1219 assert_eq!(core1.total_time_ms, 78);
1220
1221 server_thread.join().expect("server thread panicked");
1222
1223 assert_eq!(provider.residency(0), error!(EIO));
1227 assert!(matches!(*provider.state.lock(), ProviderState::Disconnected));
1228 assert_eq!(provider.residency(0), error!(EIO));
1229 assert!(matches!(*provider.state.lock(), ProviderState::Disconnected));
1230
1231 let unconfigured = CpuC2Provider::default();
1234 assert_eq!(unconfigured.residency(0), Ok(None));
1235 assert!(matches!(*unconfigured.state.lock(), ProviderState::Unavailable));
1236 }
1237
1238 #[::fuchsia::test]
1239 fn c1_time_subtracts_c2_residency_and_saturates_at_zero() {
1240 let c2 = fpowerstats::StateResidency {
1241 id: 0,
1242 name: "C2_CPU0".to_string(),
1243 total_time_ms: 1_899,
1244 entry_count: 1,
1245 last_entry_timestamp_ms: 0,
1246 };
1247
1248 assert_eq!(c1_time_us(5_000_000_000, Some(&c2)), 3_101_000);
1250
1251 assert_eq!(c1_time_us(5_000_000_000, None), 5_000_000);
1253
1254 assert_eq!(c1_time_us(1_000_000_000, Some(&c2)), 0);
1256 }
1257
1258 #[::fuchsia::test]
1259 fn min_freq_votes_manage_boost_token_lifecycle() {
1260 let (client, server) = zx::Channel::create();
1261 let (token_tx, token_rx) = std::sync::mpsc::channel::<zx::EventPair>();
1262 let server_thread = std::thread::spawn(move || {
1263 let mut executor = fuchsia_async::LocalExecutor::default();
1264 executor.run_singlethreaded(async move {
1265 let mut stream = fcpumanager::BoostRequestStream::from_channel(
1266 fuchsia_async::Channel::from_channel(server),
1267 );
1268 while let Some(fcpumanager::BoostRequest::Boost { responder }) =
1269 stream.try_next().await.expect("failed to read Boost request")
1270 {
1271 let (client_token, server_token) = zx::EventPair::create();
1272 responder.send(Ok(client_token)).expect("failed to send Boost response");
1273 token_tx.send(server_token).expect("failed to forward server token");
1274 }
1275 });
1276 });
1277
1278 let domain = CpuFreqDomain::new(
1279 &fcpu::DomainInfo {
1280 id: Some(0),
1281 core_ids: Some(vec![0, 1]),
1282 available_frequencies_hz: Some(vec![820_000_000, 1_200_000_000, 1_632_000_000]),
1283 name: Some("little".to_string()),
1284 ..Default::default()
1285 },
1286 None,
1287 );
1288 domain.state.lock().boost_proxy = Some(fcpumanager::BoostSynchronousProxy::new(client));
1289
1290 assert_eq!(domain.min_frequency_khz("powerhint_min_freq"), Some(820_000));
1291 assert_eq!(domain.min_frequency_khz("soft_min_freq"), Some(820_000));
1292 assert_eq!(domain.min_frequency_khz("scaling_min_freq"), Some(820_000));
1293
1294 domain
1297 .update_min_vote(None, "powerhint_min_freq", 1_632_000)
1298 .expect("update_min_vote must succeed");
1299 let server_token = token_rx.recv().expect("must receive a Boost token");
1300 assert_eq!(
1301 server_token
1302 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST),
1303 zx::WaitResult::TimedOut(zx::Signals::NONE)
1304 );
1305 assert_eq!(domain.min_frequency_khz("powerhint_min_freq"), Some(1_632_000));
1306 assert_eq!(domain.min_frequency_khz("soft_min_freq"), Some(820_000));
1307 assert_eq!(domain.min_frequency_khz("scaling_min_freq"), Some(1_632_000));
1308
1309 domain
1312 .update_min_vote(None, "soft_min_freq", 1_200_000)
1313 .expect("update_min_vote must succeed");
1314 assert!(token_rx.try_recv().is_err(), "must reuse existing Boost token");
1315 assert_eq!(domain.min_frequency_khz("soft_min_freq"), Some(1_200_000));
1316 assert_eq!(domain.min_frequency_khz("scaling_min_freq"), Some(1_632_000));
1317
1318 domain
1319 .update_min_vote(None, "powerhint_min_freq", 820_000)
1320 .expect("clearing powerhint_min_freq must succeed");
1321 assert_eq!(domain.min_frequency_khz("powerhint_min_freq"), Some(820_000));
1322 assert_eq!(domain.min_frequency_khz("soft_min_freq"), Some(1_200_000));
1323 assert_eq!(domain.min_frequency_khz("scaling_min_freq"), Some(1_200_000));
1324 assert_eq!(
1325 server_token
1326 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST),
1327 zx::WaitResult::TimedOut(zx::Signals::NONE)
1328 );
1329
1330 domain
1334 .update_min_vote(None, "soft_min_freq", 0)
1335 .expect("clearing soft_min_freq must succeed");
1336 assert_eq!(domain.min_frequency_khz("soft_min_freq"), Some(820_000));
1337 assert_eq!(domain.min_frequency_khz("scaling_min_freq"), Some(820_000));
1338 let closed_signals = server_token
1339 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST)
1340 .to_result()
1341 .expect("server_token must observe ZX_EVENTPAIR_PEER_CLOSED");
1342 assert!(closed_signals.contains(zx::Signals::EVENTPAIR_PEER_CLOSED));
1343
1344 drop(domain);
1345 server_thread.join().expect("server thread panicked");
1346 }
1347}