1use fidl_fuchsia_hardware_power_stats as fpowerstats;
14use starnix_core::device::power_stats::PowerStatsManager;
15use starnix_core::fs::sysfs::build_device_directory;
16use starnix_core::task::{CurrentTask, Kernel};
17use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
18use starnix_logging::log_warn;
19use starnix_uapi::errno;
20use starnix_uapi::errors::Errno;
21use starnix_uapi::file_mode::mode;
22use std::borrow::Cow;
23use std::collections::{BTreeMap, HashSet};
24use std::fmt::Write as _;
25
26const PERF_STATE_FILE_PREFIX: &str = "lpcm_";
29
30const POWER_DOMAIN_FILE_PREFIX: &str = "pwrblk_";
33
34const PLATFORM_POWER_ENTITY: &str = "soc_power_state";
36
37const PLATFORM_POWER_FILE: &str = "platform_power_residency";
39
40const RESIDENCY_DEVICE_NAME: &str = "powerdashboard";
43
44const POWER_STATE_STATS_ENTITY: &str = "cap_power_state";
46
47const POWER_STATE_STATS_FILE: &str = "power_state_stats";
49
50const CAP_SYSFS_DEVICE_NAME: &str = "cap_sysfs";
53
54const DVFS_STATS_ENTITY_PREFIX: &str = "cap_dvfs_";
58
59const DVFS_STATS_FILE_PREFIX: &str = "dvfs_stats_";
62
63const DSU_DVFS_ENTITY: &str = "cap_dvfs_clk_dsu";
66
67const DSU_DEVFREQ_DEVICE_NAME: &str = "200c0780.dsufreq";
69
70const DSU_AVAILABLE_FREQUENCIES_HZ: [u64; 21] = [
76 115_000_000,
77 268_000_000,
78 400_000_000,
79 537_000_000,
80 691_000_000,
81 768_000_000,
82 844_000_000,
83 960_000_000,
84 1_056_000_000,
85 1_132_000_000,
86 1_228_000_000,
87 1_324_000_000,
88 1_420_000_000,
89 1_516_000_000,
90 1_612_000_000,
91 1_728_000_000,
92 1_843_000_000,
93 1_920_000_000,
94 1_996_000_000,
95 2_092_000_000,
96 2_188_000_000,
97];
98
99const TIMESTAMP_TICKS_PER_MS: u64 = 38_400;
105
106type FormatResidency = fn(&fpowerstats::Stats) -> String;
108
109struct ResidencyFileSpec {
111 device: &'static str,
113 file: String,
115 entity: String,
117 format: FormatResidency,
118}
119
120pub fn init_power_residency_device(kernel: &Kernel) {
129 let mut added_devices: HashSet<&'static str> = HashSet::new();
130 let manager = PowerStatsManager::get_or_init(kernel);
131 let manager_for_files = manager.clone();
132 manager.watch_providers(kernel, move |kernel, provider| {
133 let mut files_by_device: BTreeMap<&'static str, Vec<ResidencyFileSpec>> = BTreeMap::new();
134 for spec in provider.entity_names().iter().filter_map(|name| residency_file_spec(name)) {
135 files_by_device.entry(spec.device).or_default().push(spec);
136 }
137 let registry = &kernel.device_registry;
138 for (device_name, files) in files_by_device {
139 if !added_devices.insert(device_name) {
140 log_warn!(
141 "Ignoring {device_name} power residency entities from additional provider {}",
142 provider.instance_name()
143 );
144 continue;
145 }
146 registry.add_bus_device(
147 device_name.into(),
148 Some(registry.objects.platform_device()),
149 registry.objects.platform_bus(),
150 |device, dir| {
151 build_device_directory(device, dir);
152 for spec in files {
153 let file = ResidencyFile {
154 entity: spec.entity,
155 format: spec.format,
156 manager: manager_for_files.clone(),
157 };
158 dir.entry(&spec.file, BytesFile::new_node(file), mode!(IFREG, 0o444));
159 }
160 },
161 );
162 }
163 if provider.entity_names().iter().any(|name| name == DSU_DVFS_ENTITY)
164 && added_devices.insert(DSU_DEVFREQ_DEVICE_NAME)
165 {
166 add_dsu_devfreq_device(kernel);
167 }
168 });
169}
170
171fn add_dsu_devfreq_device(kernel: &Kernel) {
174 let registry = &kernel.device_registry;
175 let platform_device = registry.add_bus_device(
176 DSU_DEVFREQ_DEVICE_NAME.into(),
177 Some(registry.objects.platform_device()),
178 registry.objects.platform_bus(),
179 |device, dir| build_device_directory(device, dir),
180 );
181 let devfreq_class = registry.objects.get_or_create_class("devfreq".into());
182 registry.add_numberless_device(
183 DSU_DEVFREQ_DEVICE_NAME.into(),
184 Some(platform_device),
185 devfreq_class,
186 |device, dir| {
187 build_device_directory(device, dir);
188 dir.entry(
189 "available_frequencies",
190 BytesFile::new_node(dsu_available_frequencies().into_bytes()),
191 mode!(IFREG, 0o444),
192 );
193 },
194 );
195}
196
197fn dsu_available_frequencies() -> String {
200 let frequencies: Vec<_> = DSU_AVAILABLE_FREQUENCIES_HZ.iter().map(u64::to_string).collect();
201 frequencies.join(" ") + "\n"
202}
203
204fn residency_file_spec(entity: &str) -> Option<ResidencyFileSpec> {
207 let prefixed = |prefix: &str| {
208 entity.len() > prefix.len()
209 && entity.starts_with(prefix)
210 && entity.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
211 };
212 let (device, file, format): (_, _, FormatResidency) = if prefixed(PERF_STATE_FILE_PREFIX) {
213 (RESIDENCY_DEVICE_NAME, entity.to_string(), format_performance_states)
214 } else if prefixed(POWER_DOMAIN_FILE_PREFIX) {
215 (RESIDENCY_DEVICE_NAME, entity.to_string(), format_power_domain_states)
216 } else if entity == PLATFORM_POWER_ENTITY {
217 (RESIDENCY_DEVICE_NAME, PLATFORM_POWER_FILE.to_string(), format_platform_power_states)
218 } else if prefixed(DVFS_STATS_ENTITY_PREFIX) {
219 let file = format!("{DVFS_STATS_FILE_PREFIX}{}", &entity[DVFS_STATS_ENTITY_PREFIX.len()..]);
220 (CAP_SYSFS_DEVICE_NAME, file, format_dvfs_stats)
221 } else if entity == POWER_STATE_STATS_ENTITY {
222 (CAP_SYSFS_DEVICE_NAME, POWER_STATE_STATS_FILE.to_string(), format_power_state_stats)
223 } else {
224 return None;
225 };
226 Some(ResidencyFileSpec { device, file, entity: entity.to_string(), format })
227}
228
229struct ResidencyFile {
231 entity: String,
232 format: FormatResidency,
233 manager: PowerStatsManager,
234}
235
236impl BytesFileOps for ResidencyFile {
237 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
238 let stats = self.manager.entity_stats(&self.entity)?.ok_or_else(|| {
239 log_warn!("No power stats provider registered for {}", self.entity);
240 errno!(EIO)
241 })?;
242 Ok((self.format)(&stats).into_bytes().into())
243 }
244}
245
246fn safe_states(stats: &fpowerstats::Stats) -> impl Iterator<Item = &fpowerstats::StateResidency> {
252 stats.state_residency_data.iter().filter(|state| {
253 if state.name.chars().any(char::is_control) {
254 log_warn!("Leaving out {} state {} with an unsafe name", stats.entity, state.id);
255 return false;
256 }
257 true
258 })
259}
260
261fn format_performance_states(stats: &fpowerstats::Stats) -> String {
266 let mut out = String::new();
267 let _ = writeln!(out, "num_pf_states: {}", stats.state_residency_data.len());
268 for state in &stats.state_residency_data {
269 let time_in_state_us = state.total_time_ms.saturating_mul(1000);
270 let _ = writeln!(out, "pf_state: {}", state.id);
271 let _ = writeln!(out, "pf_entry_count: {}", state.entry_count);
272 let _ = writeln!(out, "pf_time_in_state: {time_in_state_us} (us)\n");
273 }
274 out
275}
276
277fn format_power_domain_states(stats: &fpowerstats::Stats) -> String {
287 let mut out = String::new();
288 let _ = writeln!(out, "\nSSWRP lpb\n");
289 for state in safe_states(stats) {
290 let time_in_state_us = state.total_time_ms.saturating_mul(1000);
291 let last_entry_us = state.last_entry_timestamp_ms.saturating_mul(1000);
292 let _ = writeln!(out, "power_state: {}", state.name);
293 let _ = writeln!(out, "entry_count: {}", state.entry_count);
294 let _ = writeln!(out, "time_in_state: {time_in_state_us} (us)");
295 let _ = writeln!(out, "last_entry_timestamp: {last_entry_us}");
296 let _ = writeln!(out, "last_exit_timestamp: 0");
297 }
298 out.push('\n');
299 out
300}
301
302fn format_platform_power_states(stats: &fpowerstats::Stats) -> String {
311 let mut out = String::new();
312 for state in safe_states(stats) {
313 let last_entry_ticks = state.last_entry_timestamp_ms.saturating_mul(TIMESTAMP_TICKS_PER_MS);
314 let _ = writeln!(out, "power_state: {}", state.name);
315 let _ = writeln!(out, "entry_count: {}", state.entry_count);
316 let _ = writeln!(out, "time_in_state_ms: {}", state.total_time_ms);
317 let _ = writeln!(out, "last_entry_timestamp_ticks: {last_entry_ticks}");
318 let _ = writeln!(out, "last_exit_timestamp_ticks: 0\n");
319 }
320 out
321}
322
323fn format_power_state_stats(stats: &fpowerstats::Stats) -> String {
332 let mut out = String::new();
333 for state in safe_states(stats) {
334 let last_entry_ticks = state.last_entry_timestamp_ms.saturating_mul(TIMESTAMP_TICKS_PER_MS);
335 let _ = writeln!(out, "---");
336 let _ = writeln!(out, "state: {}", state.name);
337 let _ = writeln!(out, "last_entry_ts_ticks: {last_entry_ticks}");
338 let _ = writeln!(out, "last_exit_ts_ticks: 0");
339 let _ = writeln!(out, "total_state_residency_ms: {}", state.total_time_ms);
340 let _ = writeln!(out, "last_state_entry_counts: {}", state.entry_count);
341 }
342 out
343}
344
345fn format_dvfs_stats(stats: &fpowerstats::Stats) -> String {
351 let clock_domain = stats.entity.strip_prefix(DVFS_STATS_ENTITY_PREFIX).unwrap_or(&stats.entity);
352 let mut out = String::new();
353 let _ = writeln!(out, "---");
354 let _ = writeln!(out, "clock_domain: {clock_domain}");
355 let _ = writeln!(out, "per_op_level_stats: ");
356 for state in &stats.state_residency_data {
357 let last_entry_ticks = state.last_entry_timestamp_ms.saturating_mul(TIMESTAMP_TICKS_PER_MS);
358 let _ = writeln!(out, " - oplevel: {}", state.id);
359 let _ = writeln!(out, " entry_count: {}", state.entry_count);
360 let _ = writeln!(out, " total_residency_ms: {}", state.total_time_ms);
361 let _ = writeln!(out, " last_entry_timestamp_ticks: {last_entry_ticks}");
362 }
363 out
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use fidl::endpoints::{ServerEnd, create_sync_proxy};
370 use fuchsia_async as fasync;
371 use futures::StreamExt;
372 use starnix_core::device::power_stats::PowerStatsProvider;
373 use starnix_core::testing::spawn_kernel_and_run_sync;
374 use starnix_uapi::error;
375
376 fn state(id: i32, entry_count: u32, total_time_ms: u64) -> fpowerstats::StateResidency {
377 fpowerstats::StateResidency {
378 id,
379 name: id.to_string(),
380 total_time_ms,
381 entry_count,
382 last_entry_timestamp_ms: 0,
383 }
384 }
385
386 fn stats(
387 entity: &str,
388 state_residency_data: Vec<fpowerstats::StateResidency>,
389 ) -> fpowerstats::Stats {
390 fpowerstats::Stats { entity: entity.to_string(), state_residency_data }
391 }
392
393 async fn serve_provider(
395 stream: fpowerstats::DeviceRequestStream,
396 reports: Vec<fpowerstats::Stats>,
397 ) {
398 let _ = stream
399 .for_each(|request| {
400 let reports = &reports;
401 async move {
402 if let Ok(fpowerstats::DeviceRequest::GetStats { responder }) = request {
403 let _ = responder.send(reports);
404 }
405 }
406 })
407 .await;
408 }
409
410 fn spawn_server<F: Future<Output = ()> + 'static>(serve: impl FnOnce() -> F + Send + 'static) {
412 std::thread::spawn(move || fasync::LocalExecutor::default().run_singlethreaded(serve()));
413 }
414
415 fn provider_with_entities(
418 instance_name: &str,
419 entity_names: impl IntoIterator<Item = impl Into<String>>,
420 ) -> PowerStatsProvider {
421 fake_provider(
422 instance_name,
423 entity_names.into_iter().map(|name| stats(&name.into(), vec![])).collect(),
424 )
425 }
426
427 fn fake_provider(instance_name: &str, reports: Vec<fpowerstats::Stats>) -> PowerStatsProvider {
429 let entity_names: Vec<_> = reports.iter().map(|stats| stats.entity.clone()).collect();
430 let (proxy, server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
431 spawn_server(move || serve_provider(server.into_stream(), reports));
432 PowerStatsProvider::new(instance_name, entity_names, proxy)
433 }
434
435 fn perf_file(entity: &str, manager: PowerStatsManager) -> ResidencyFile {
437 ResidencyFile { entity: entity.to_string(), format: format_performance_states, manager }
438 }
439
440 fn exists(kernel: &Kernel, path: &str) -> bool {
441 kernel.device_registry.objects.root.lookup(path.into()).is_some()
442 }
443
444 #[::fuchsia::test]
445 async fn device_is_added_with_served_entities() {
446 spawn_kernel_and_run_sync(|current_task| {
447 let kernel = current_task.kernel();
448 let prefix = PERF_STATE_FILE_PREFIX;
449 PowerStatsManager::add_provider_for_testing(
450 kernel,
451 provider_with_entities(
452 "default",
453 [
454 format!("{prefix}a"),
455 format!("{prefix}b"),
456 PLATFORM_POWER_ENTITY.to_string(),
457 "other".to_string(),
458 ],
459 ),
460 );
461 init_power_residency_device(kernel);
462
463 let device = format!("devices/platform/{RESIDENCY_DEVICE_NAME}");
464 assert!(exists(kernel, &device));
465 assert!(exists(kernel, &format!("bus/platform/devices/{RESIDENCY_DEVICE_NAME}")));
466 assert!(exists(kernel, &format!("{device}/uevent")));
467 assert!(exists(kernel, &format!("{device}/{prefix}a")));
468 assert!(exists(kernel, &format!("{device}/{prefix}b")));
469 assert!(exists(kernel, &format!("{device}/{PLATFORM_POWER_FILE}")));
470 assert!(!exists(kernel, &format!("{device}/other")));
471 })
472 .await;
473 }
474
475 #[::fuchsia::test]
476 async fn no_device_without_served_entities() {
477 spawn_kernel_and_run_sync(|current_task| {
478 let kernel = current_task.kernel();
479 PowerStatsManager::add_provider_for_testing(
480 kernel,
481 provider_with_entities("default", ["other"]),
482 );
483 init_power_residency_device(kernel);
484
485 assert!(!exists(kernel, &format!("devices/platform/{RESIDENCY_DEVICE_NAME}")));
486 assert!(!exists(kernel, &format!("devices/platform/{CAP_SYSFS_DEVICE_NAME}")));
487 })
488 .await;
489 }
490
491 #[::fuchsia::test]
492 async fn device_is_added_when_later_provider_reports_served_entities() {
493 spawn_kernel_and_run_sync(|current_task| {
494 let kernel = current_task.kernel();
495 let prefix = PERF_STATE_FILE_PREFIX;
496 let device = format!("devices/platform/{RESIDENCY_DEVICE_NAME}");
497
498 let _ = PowerStatsManager::get_or_init_for_testing(kernel);
499 init_power_residency_device(kernel);
500 assert!(!exists(kernel, &device));
501
502 PowerStatsManager::add_provider_for_testing(
503 kernel,
504 provider_with_entities("unserved", ["other"]),
505 );
506 assert!(!exists(kernel, &device));
507
508 PowerStatsManager::add_provider_for_testing(
509 kernel,
510 provider_with_entities(
511 "served",
512 [format!("{prefix}a"), PLATFORM_POWER_ENTITY.to_string()],
513 ),
514 );
515 assert!(exists(kernel, &device));
516 assert!(exists(kernel, &format!("{device}/{prefix}a")));
517 assert!(exists(kernel, &format!("{device}/{PLATFORM_POWER_FILE}")));
518 })
519 .await;
520 }
521
522 #[::fuchsia::test]
523 async fn only_first_provider_is_served() {
524 spawn_kernel_and_run_sync(|current_task| {
525 let kernel = current_task.kernel();
526 let prefix = PERF_STATE_FILE_PREFIX;
527 PowerStatsManager::add_provider_for_testing(
528 kernel,
529 provider_with_entities("first", [format!("{prefix}a")]),
530 );
531 init_power_residency_device(kernel);
532 PowerStatsManager::add_provider_for_testing(
533 kernel,
534 provider_with_entities("second", [format!("{prefix}b")]),
535 );
536
537 let device = format!("devices/platform/{RESIDENCY_DEVICE_NAME}");
538 assert!(exists(kernel, &format!("{device}/{prefix}a")));
539 assert!(!exists(kernel, &format!("{device}/{prefix}b")));
540 })
541 .await;
542 }
543
544 #[::fuchsia::test]
545 async fn each_device_is_added_for_first_provider_serving_it() {
546 spawn_kernel_and_run_sync(|current_task| {
547 let kernel = current_task.kernel();
548 let prefix = PERF_STATE_FILE_PREFIX;
549 PowerStatsManager::add_provider_for_testing(
550 kernel,
551 provider_with_entities("first", [format!("{prefix}a")]),
552 );
553 init_power_residency_device(kernel);
554 PowerStatsManager::add_provider_for_testing(
555 kernel,
556 provider_with_entities(
557 "second",
558 [format!("{prefix}b"), POWER_STATE_STATS_ENTITY.to_string()],
559 ),
560 );
561
562 let residency_device = format!("devices/platform/{RESIDENCY_DEVICE_NAME}");
563 assert!(exists(kernel, &format!("{residency_device}/{prefix}a")));
564 assert!(!exists(kernel, &format!("{residency_device}/{prefix}b")));
565 let stats_device = format!("devices/platform/{CAP_SYSFS_DEVICE_NAME}");
566 assert!(exists(kernel, &format!("bus/platform/devices/{CAP_SYSFS_DEVICE_NAME}")));
567 assert!(exists(kernel, &format!("{stats_device}/{POWER_STATE_STATS_FILE}")));
568 })
569 .await;
570 }
571
572 #[::fuchsia::test]
573 async fn read_returns_formatted_residency() {
574 spawn_kernel_and_run_sync(|current_task| {
575 let kernel = current_task.kernel();
576 let entities = [
577 stats(
578 "lpcm_a",
579 vec![state(
580 0, 11,
581 2200,
582 )],
583 ),
584 stats(
585 "pwrblk_a",
586 vec![state(
587 0, 5,
588 40,
589 )],
590 ),
591 stats(
592 PLATFORM_POWER_ENTITY,
593 vec![state(
594 0, 3,
595 100,
596 )],
597 ),
598 stats(
599 POWER_STATE_STATS_ENTITY,
600 vec![state(
601 0, 6,
602 250,
603 )],
604 ),
605 ];
606 PowerStatsManager::add_provider_for_testing(
607 kernel,
608 fake_provider("test", entities.to_vec()),
609 );
610 let manager = PowerStatsManager::get_or_init(kernel);
611
612 for entity in &entities {
613 let spec = residency_file_spec(&entity.entity).expect("entity is served");
614 let file = ResidencyFile {
615 entity: spec.entity,
616 format: spec.format,
617 manager: manager.clone(),
618 };
619 assert_eq!(
620 &*file.read(current_task).expect("read residency file"),
621 (spec.format)(entity).as_bytes(),
622 "{}",
623 entity.entity
624 );
625 }
626 })
627 .await;
628 }
629
630 #[::fuchsia::test]
631 async fn read_fails_when_entity_is_no_longer_reported() {
632 spawn_kernel_and_run_sync(|current_task| {
633 let kernel = current_task.kernel();
634 let (proxy, server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
635 let reports = vec![stats("b", vec![])];
636 spawn_server(move || serve_provider(server.into_stream(), reports));
637 PowerStatsManager::add_provider_for_testing(
638 kernel,
639 PowerStatsProvider::new("test", ["a", "b"], proxy),
640 );
641 let file = perf_file("a", PowerStatsManager::get_or_init(kernel));
642
643 assert_eq!(file.read(current_task).map(|_| ()), error!(EIO));
644 })
645 .await;
646 }
647
648 #[::fuchsia::test]
649 async fn read_fails_when_provider_is_gone_and_recovers_on_restart() {
650 spawn_kernel_and_run_sync(|current_task| {
651 let kernel = current_task.kernel();
652 let (proxy, server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
653 drop::<ServerEnd<_>>(server);
654 PowerStatsManager::add_provider_for_testing(
655 kernel,
656 PowerStatsProvider::new("test", ["a", "b"], proxy),
657 );
658 let manager = PowerStatsManager::get_or_init(kernel);
659 let file_a = perf_file("a", manager.clone());
660 let file_b = perf_file("b", manager);
661
662 assert_eq!(file_a.read(current_task).map(|_| ()), error!(EIO));
663 assert_eq!(file_b.read(current_task).map(|_| ()), error!(EIO));
664
665 let entity = stats(
666 "a",
667 vec![state(
668 0, 5, 100,
669 )],
670 );
671 PowerStatsManager::add_provider_for_testing(
672 kernel,
673 fake_provider("test_restarted", vec![entity.clone()]),
674 );
675 let contents = file_a.read(current_task).expect("read after restart");
676 assert_eq!(&*contents, format_performance_states(&entity).as_bytes());
677 assert_eq!(file_b.read(current_task).map(|_| ()), error!(EIO));
678 })
679 .await;
680 }
681
682 #[::fuchsia::test]
683 async fn read_fails_when_provider_does_not_respond() {
684 spawn_kernel_and_run_sync(|current_task| {
685 let kernel = current_task.kernel();
686 let (proxy, _server) = create_sync_proxy::<fpowerstats::DeviceMarker>();
688 PowerStatsManager::add_provider_for_testing(
689 kernel,
690 PowerStatsProvider::new("test", ["a"], proxy),
691 );
692 let file = perf_file("a", PowerStatsManager::get_or_init(kernel));
693
694 assert_eq!(file.read(current_task).map(|_| ()), error!(EIO));
695 })
696 .await;
697 }
698
699 #[::fuchsia::test]
700 fn performance_states_are_formatted_per_state() {
701 let stats = stats(
702 "a",
703 vec![
704 state(
705 0, 11, 2200,
706 ),
707 state(1, 4, 7),
708 ],
709 );
710
711 assert_eq!(
712 format_performance_states(&stats),
713 "num_pf_states: 2\n\
714 pf_state: 0\npf_entry_count: 11\npf_time_in_state: 2200000 (us)\n\n\
715 pf_state: 1\npf_entry_count: 4\npf_time_in_state: 7000 (us)\n\n"
716 );
717 }
718
719 #[::fuchsia::test]
720 fn power_domain_states_are_formatted_per_state() {
721 let off = fpowerstats::StateResidency {
722 name: "STATE_OFF".to_string(),
723 last_entry_timestamp_ms: 5,
724 ..state(0, 3, 12)
725 };
726 let on = fpowerstats::StateResidency {
727 name: "STATE_ON".to_string(),
728 last_entry_timestamp_ms: 9,
729 ..state(1, 4, 7)
730 };
731 let stats = stats("a", vec![off, on]);
732
733 assert_eq!(
734 format_power_domain_states(&stats),
735 "\nSSWRP lpb\n\n\
736 power_state: STATE_OFF\nentry_count: 3\ntime_in_state: 12000 (us)\n\
737 last_entry_timestamp: 5000\nlast_exit_timestamp: 0\n\
738 power_state: STATE_ON\nentry_count: 4\ntime_in_state: 7000 (us)\n\
739 last_entry_timestamp: 9000\nlast_exit_timestamp: 0\n\n"
740 );
741 }
742
743 #[::fuchsia::test]
744 fn platform_power_states_are_formatted_per_state() {
745 let active = fpowerstats::StateResidency {
746 name: "ACTIVE".to_string(),
747 last_entry_timestamp_ms: 2,
748 ..state(0, 3, 100)
749 };
750 let stats = stats(
751 "a",
752 vec![active, state(1, 0, 0)],
753 );
754
755 assert_eq!(
756 format_platform_power_states(&stats),
757 "power_state: ACTIVE\nentry_count: 3\ntime_in_state_ms: 100\n\
758 last_entry_timestamp_ticks: 76800\nlast_exit_timestamp_ticks: 0\n\n\
759 power_state: 1\nentry_count: 0\ntime_in_state_ms: 0\n\
760 last_entry_timestamp_ticks: 0\nlast_exit_timestamp_ticks: 0\n\n"
761 );
762 }
763
764 #[::fuchsia::test]
765 fn power_state_stats_are_formatted_per_state() {
766 let cluster_off = fpowerstats::StateResidency {
767 name: "Cluster 0 off".to_string(),
768 last_entry_timestamp_ms: 2,
769 ..state(0, 6, 250)
770 };
771 let suspend = fpowerstats::StateResidency {
772 name: "Suspend".to_string(),
773 ..state(1, 1, 10)
774 };
775 let stats = stats(POWER_STATE_STATS_ENTITY, vec![cluster_off, suspend]);
776
777 assert_eq!(
778 format_power_state_stats(&stats),
779 "---\nstate: Cluster 0 off\nlast_entry_ts_ticks: 76800\nlast_exit_ts_ticks: 0\n\
780 total_state_residency_ms: 250\nlast_state_entry_counts: 6\n\
781 ---\nstate: Suspend\nlast_entry_ts_ticks: 0\nlast_exit_ts_ticks: 0\n\
782 total_state_residency_ms: 10\nlast_state_entry_counts: 1\n"
783 );
784 }
785
786 #[::fuchsia::test]
787 fn dvfs_stats_are_formatted_per_level() {
788 let fast = fpowerstats::StateResidency {
789 last_entry_timestamp_ms: 3,
790 ..state(0, 7, 120)
791 };
792 let stats = stats(
793 "cap_dvfs_clk_lit",
794 vec![fast, state(1, 0, 0)],
795 );
796
797 assert_eq!(
798 format_dvfs_stats(&stats),
799 "---\nclock_domain: clk_lit\nper_op_level_stats: \n\
800 \x20 - oplevel: 0\n entry_count: 7\n total_residency_ms: 120\n\
801 \x20 last_entry_timestamp_ticks: 115200\n\
802 \x20 - oplevel: 1\n entry_count: 0\n total_residency_ms: 0\n\
803 \x20 last_entry_timestamp_ticks: 0\n"
804 );
805 }
806
807 #[::fuchsia::test]
808 fn residency_with_no_states() {
809 let stats = stats("a", vec![]);
810
811 assert_eq!(format_performance_states(&stats), "num_pf_states: 0\n");
812 assert_eq!(format_power_domain_states(&stats), "\nSSWRP lpb\n\n\n");
813 assert_eq!(format_platform_power_states(&stats), "");
814 assert_eq!(format_power_state_stats(&stats), "");
815 assert_eq!(format_dvfs_stats(&stats), "---\nclock_domain: a\nper_op_level_stats: \n");
816 }
817
818 #[::fuchsia::test]
819 fn states_with_unsafe_names_are_left_out() {
820 let unsafe_state = fpowerstats::StateResidency {
821 name: "A\nentry_count: 9".to_string(),
822 ..state(0, 1, 1)
823 };
824 let stats = stats(
825 "a",
826 vec![unsafe_state, state(1, 2, 2)],
827 );
828
829 assert_eq!(
830 format_power_domain_states(&stats),
831 "\nSSWRP lpb\n\n\
832 power_state: 1\nentry_count: 2\ntime_in_state: 2000 (us)\n\
833 last_entry_timestamp: 0\nlast_exit_timestamp: 0\n\n"
834 );
835 assert_eq!(
836 format_platform_power_states(&stats),
837 "power_state: 1\nentry_count: 2\ntime_in_state_ms: 2\n\
838 last_entry_timestamp_ticks: 0\nlast_exit_timestamp_ticks: 0\n\n"
839 );
840 assert_eq!(
841 format_power_state_stats(&stats),
842 "---\nstate: 1\nlast_entry_ts_ticks: 0\nlast_exit_ts_ticks: 0\n\
843 total_state_residency_ms: 2\nlast_state_entry_counts: 2\n"
844 );
845 }
846
847 #[::fuchsia::test]
848 fn only_served_entities_with_safe_names_are_mapped_to_files() {
849 for prefix in [PERF_STATE_FILE_PREFIX, POWER_DOMAIN_FILE_PREFIX] {
850 let names = [
851 format!("{prefix}a"),
852 format!("{prefix}b_1"),
853 PLATFORM_POWER_ENTITY.to_string(),
854 POWER_STATE_STATS_ENTITY.to_string(),
855 "other".to_string(),
856 prefix.to_string(),
857 format!("{prefix}../x"),
858 format!("{prefix}a/b"),
859 prefix.to_uppercase() + "A",
860 format!("x{prefix}a"),
861 format!("{PLATFORM_POWER_ENTITY}_x"),
862 format!("{POWER_STATE_STATS_ENTITY}_x"),
863 ];
864
865 let served: Vec<_> = names
866 .into_iter()
867 .filter_map(|name| residency_file_spec(&name).map(|spec| (spec.device, spec.file)))
868 .collect();
869 assert_eq!(
870 served,
871 vec![
872 (RESIDENCY_DEVICE_NAME, format!("{prefix}a")),
873 (RESIDENCY_DEVICE_NAME, format!("{prefix}b_1")),
874 (RESIDENCY_DEVICE_NAME, PLATFORM_POWER_FILE.to_string()),
875 (CAP_SYSFS_DEVICE_NAME, POWER_STATE_STATS_FILE.to_string()),
876 ]
877 );
878 }
879 }
880
881 #[::fuchsia::test]
882 fn dvfs_entities_are_mapped_to_cap_sysfs_files() {
883 let names = [
884 "cap_dvfs_clk_lit",
885 "cap_dvfs_clk_mid0",
886 "cap_dvfs_",
887 "cap_dvfs_../x",
888 "cap_dvfs_a-b",
889 "xcap_dvfs_a",
890 "dvfs_gpu",
892 ];
893
894 let served: Vec<_> = names
895 .into_iter()
896 .filter_map(|name| residency_file_spec(name).map(|spec| (spec.device, spec.file)))
897 .collect();
898 assert_eq!(
899 served,
900 vec![
901 (CAP_SYSFS_DEVICE_NAME, "dvfs_stats_clk_lit".to_string()),
902 (CAP_SYSFS_DEVICE_NAME, "dvfs_stats_clk_mid0".to_string()),
903 ]
904 );
905 }
906
907 #[::fuchsia::test]
908 async fn dsu_devfreq_device_is_added_with_dsu_entity() {
909 spawn_kernel_and_run_sync(|current_task| {
910 let kernel = current_task.kernel();
911 PowerStatsManager::add_provider_for_testing(
912 kernel,
913 provider_with_entities("default", [DSU_DVFS_ENTITY]),
914 );
915 init_power_residency_device(kernel);
916
917 let device = format!("devices/platform/{DSU_DEVFREQ_DEVICE_NAME}");
918 assert!(exists(kernel, &format!("bus/platform/devices/{DSU_DEVFREQ_DEVICE_NAME}")));
919 assert!(exists(kernel, &format!("{device}/uevent")));
920 assert!(exists(
921 kernel,
922 &format!("{device}/devfreq/{DSU_DEVFREQ_DEVICE_NAME}/available_frequencies")
923 ));
924 assert!(exists(kernel, &format!("class/devfreq/{DSU_DEVFREQ_DEVICE_NAME}")));
925 })
926 .await;
927 }
928
929 #[::fuchsia::test]
930 async fn no_dsu_devfreq_device_without_dsu_entity() {
931 spawn_kernel_and_run_sync(|current_task| {
932 let kernel = current_task.kernel();
933 PowerStatsManager::add_provider_for_testing(
934 kernel,
935 provider_with_entities("default", ["cap_dvfs_clk_lit"]),
936 );
937 init_power_residency_device(kernel);
938
939 assert!(exists(kernel, &format!("devices/platform/{CAP_SYSFS_DEVICE_NAME}")));
940 assert!(!exists(kernel, &format!("devices/platform/{DSU_DEVFREQ_DEVICE_NAME}")));
941 })
942 .await;
943 }
944
945 #[::fuchsia::test]
946 async fn dsu_devfreq_device_is_added_once() {
947 spawn_kernel_and_run_sync(|current_task| {
948 let kernel = current_task.kernel();
949 PowerStatsManager::add_provider_for_testing(
950 kernel,
951 provider_with_entities("first", [DSU_DVFS_ENTITY]),
952 );
953 init_power_residency_device(kernel);
954 PowerStatsManager::add_provider_for_testing(
955 kernel,
956 provider_with_entities("second", [DSU_DVFS_ENTITY]),
957 );
958
959 assert!(exists(kernel, &format!("devices/platform/{DSU_DEVFREQ_DEVICE_NAME}")));
960 })
961 .await;
962 }
963
964 #[::fuchsia::test]
965 fn dsu_available_frequencies_are_listed_from_slowest() {
966 assert!(DSU_AVAILABLE_FREQUENCIES_HZ.is_sorted_by(|a, b| a < b));
967 assert_eq!(
968 dsu_available_frequencies(),
969 "115000000 268000000 400000000 537000000 691000000 768000000 844000000 960000000 \
970 1056000000 1132000000 1228000000 1324000000 1420000000 1516000000 1612000000 \
971 1728000000 1843000000 1920000000 1996000000 2092000000 2188000000\n"
972 );
973 }
974}