1use anyhow::Context as _;
6use fidl_fuchsia_gpu_magma as fmagma;
7use fidl_fuchsia_power_broker as fpbroker;
8use fuchsia_async as fasync;
9use fuchsia_component::client::{self as fclient, connect_to_protocol_sync};
10use futures::TryStreamExt;
11use itertools::Itertools;
12use starnix_core::fs::sysfs::build_device_directory;
13use starnix_core::task::{CurrentTask, Kernel};
14use starnix_core::vfs::FsNodeOps;
15use starnix_core::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
16use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps, parse_unsigned_file};
17use starnix_logging::{log_info, log_warn};
18use starnix_sync::{LockDepMutex, MagmaPowerDomainLock};
19use starnix_uapi::errors::Errno;
20use starnix_uapi::file_mode::mode;
21use starnix_uapi::{errno, error};
22use std::borrow::Cow;
23use std::collections::HashMap;
24use std::sync::Arc;
25
26const DEFAULT_GPU_DEVICE_NAME: &str = "34f00000.gpu0";
28
29const DEFAULT_GPU_GENPD_DEVICE_NAME: &str = "genpd:0:34f00000.gpu0";
31
32const FIDL_CALL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
34
35fn fidl_deadline() -> zx::MonotonicInstant {
36 zx::MonotonicInstant::after(FIDL_CALL_TIMEOUT)
37}
38
39const DEFAULT_GPU_AVAILABLE_FREQUENCIES_HZ: &[u64] = &[
43 198_000_000, 253_000_000, 300_000_000, 396_000_000, 448_000_000, 512_000_000, 576_000_000, 633_000_000, 691_000_000, 748_000_000, 806_000_000, 864_000_000, 921_000_000, 998_000_000, 1_094_000_000, ];
59
60const DEFAULT_GPU_FREQ_HZ: u64 = 396_000_000;
61const DEFAULT_POLLING_INTERVAL_MS: u64 = 20;
62
63#[cfg_attr(not(test), allow(dead_code))]
65const GPU_POWER_LEVEL_OFF: fpbroker::PowerLevel = 0;
66
67const GPU_POWER_LEVEL_ON: fpbroker::PowerLevel = 1;
69
70#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct GpuDeviceConfig {
74 pub device_name: String,
76
77 pub genpd_device_name: String,
79
80 pub available_frequencies_hz: Vec<u64>,
84
85 pub default_freq_hz: u64,
87}
88
89impl Default for GpuDeviceConfig {
90 fn default() -> Self {
91 Self {
92 device_name: DEFAULT_GPU_DEVICE_NAME.to_string(),
93 genpd_device_name: DEFAULT_GPU_GENPD_DEVICE_NAME.to_string(),
94 available_frequencies_hz: DEFAULT_GPU_AVAILABLE_FREQUENCIES_HZ.to_vec(),
95 default_freq_hz: DEFAULT_GPU_FREQ_HZ,
96 }
97 }
98}
99
100impl GpuDeviceConfig {
101 fn min_freq_hz(&self) -> u64 {
102 self.available_frequencies_hz.first().copied().unwrap_or(self.default_freq_hz)
103 }
104
105 fn max_freq_hz(&self) -> u64 {
106 self.available_frequencies_hz.last().copied().unwrap_or(self.default_freq_hz)
107 }
108
109 fn snap_min_freq_hz(&self, hz: u64) -> Option<u64> {
112 if hz == 0 {
113 return None;
114 }
115 Some(
116 self.available_frequencies_hz
117 .iter()
118 .copied()
119 .find(|&opp_hz| opp_hz >= hz)
120 .unwrap_or_else(|| self.max_freq_hz()),
121 )
122 }
123
124 fn snap_max_freq_hz(&self, hz: u64) -> Option<u64> {
127 let max_hz = self.max_freq_hz();
128 if hz == 0 || hz >= max_hz {
129 return None;
130 }
131 Some(
132 self.available_frequencies_hz
133 .iter()
134 .rev()
135 .copied()
136 .find(|&opp_hz| opp_hz <= hz)
137 .unwrap_or_else(|| self.min_freq_hz()),
138 )
139 }
140}
141
142const KHZ_TO_HZ_THRESHOLD: u64 = 10_000_000;
145
146const VOTE_MANAGER_MIN_FREQ_NODES: &[&str] =
147 &["powerhint_min_freq", "soft_min_freq", "debug_min_freq"];
148
149const VOTE_MANAGER_MAX_FREQ_NODES: &[&str] =
150 &["powerhint_max_freq", "thermal_max_freq", "soft_max_freq", "debug_max_freq"];
151
152fn spawn_power_service_watcher(kernel: &Kernel, domain: Arc<GpuPowerDomain>) {
153 kernel.kthreads.spawn_future(
154 move || async move {
155 let service = match fclient::Service::open(fmagma::PowerServiceMarker) {
156 Ok(service) => service,
157 Err(err) => {
158 log_info!("GPU PowerService directory unavailable: {err:#}");
159 return;
160 }
161 };
162 if let Err(err) = watch_power_service(domain, service).await {
163 log_info!("GPU PowerService watcher exited: {err:#}");
164 }
165 },
166 "gpu_power_service_watcher",
167 );
168}
169
170async fn watch_power_service(
173 domain: Arc<GpuPowerDomain>,
174 service: fclient::Service<fmagma::PowerServiceMarker>,
175) -> Result<(), anyhow::Error> {
176 let mut instances = service.watch().await.context("failed to watch GPU PowerService")?;
177 while let Some(instance) = instances.try_next().await? {
178 let name = instance.instance_name().to_string();
179 let provider = match instance.connect_to_power_element_provider_sync() {
180 Ok(provider) => Arc::new(provider),
181 Err(err) => {
182 log_warn!("Failed to connect to GPU PowerElementProvider {name}: {err:#}");
183 continue;
184 }
185 };
186 let domain_clone = Arc::clone(&domain);
187 fasync::unblock(move || {
188 domain_clone.set_provider(provider);
189 })
190 .await;
191 }
192 Ok(())
193}
194
195pub(crate) fn register_gpu_devices(kernel: &Kernel, config: GpuDeviceConfig) {
199 let device_name = config.device_name.clone();
200 let genpd_device_name = config.genpd_device_name.clone();
201 let domain = Arc::new(GpuPowerDomain::new(config));
202 spawn_power_service_watcher(kernel, Arc::clone(&domain));
203 let registry = &kernel.device_registry;
204 let objects = ®istry.objects;
205
206 let domain_for_genpd = domain.clone();
208 registry.add_subsystemless_device(
209 genpd_device_name.as_str().into(),
210 None,
211 move |device, dir| {
212 build_device_directory(device, dir);
213 build_gpu_power_directory(dir, &domain_for_genpd);
214 },
215 );
216
217 let domain_for_platform = domain.clone();
219 let gpu_platform_device = registry.add_bus_device(
220 device_name.as_str().into(),
221 Some(objects.platform_device()),
222 objects.platform_bus(),
223 move |device, dir| {
224 build_device_directory(device, dir);
225 build_gpu_power_directory(dir, &domain_for_platform);
226 },
227 );
228
229 let devfreq_class = objects.get_or_create_class("devfreq".into());
230 registry.add_numberless_device(
231 device_name.as_str().into(),
232 Some(gpu_platform_device),
233 devfreq_class,
234 move |device, dir| {
235 build_device_directory(device, dir);
236 build_gpu_devfreq_directory(dir, &domain);
237 },
238 );
239}
240
241fn build_gpu_power_directory(dir: &SimpleDirectoryMutator, domain: &Arc<GpuPowerDomain>) {
242 dir.subdir("power", 0o755, |dir| {
243 dir.entry("control", GpuPowerControlFile::new_node(domain.clone()), mode!(IFREG, 0o644));
244 dir.entry(
245 "runtime_status",
246 GpuRuntimeStatusFile::new_node(domain.clone()),
247 mode!(IFREG, 0o444),
248 );
249 });
250}
251
252fn build_gpu_devfreq_directory(dir: &SimpleDirectoryMutator, domain: &Arc<GpuPowerDomain>) {
253 dir.entry(
254 "available_frequencies",
255 BytesFile::new_node(
256 (domain.config.available_frequencies_hz.iter().join(" ") + "\n").into_bytes(),
257 ),
258 mode!(IFREG, 0o444),
259 );
260 dir.entry("cur_freq", GpuCurFreqFile::new_node(domain.clone()), mode!(IFREG, 0o444));
261 dir.entry(
262 "min_freq",
263 GpuFreqVoteFile::<false>::new_node(domain.clone(), "min_freq"),
264 mode!(IFREG, 0o644),
265 );
266 dir.entry(
267 "max_freq",
268 GpuFreqVoteFile::<true>::new_node(domain.clone(), "max_freq"),
269 mode!(IFREG, 0o644),
270 );
271 dir.entry(
272 "polling_interval",
273 GpuPollingIntervalFile::new_node(domain.clone()),
274 mode!(IFREG, 0o644),
275 );
276 dir.entry("governor", BytesFile::new_node(b"simple_ondemand\n".to_vec()), mode!(IFREG, 0o444));
277
278 dir.subdir("vote_manager", 0o755, |dir| {
279 for &voter in VOTE_MANAGER_MIN_FREQ_NODES {
280 dir.entry(
281 voter,
282 GpuFreqVoteFile::<false>::new_node(domain.clone(), voter),
283 mode!(IFREG, 0o644),
284 );
285 }
286 for &voter in VOTE_MANAGER_MAX_FREQ_NODES {
287 dir.entry(
288 voter,
289 GpuFreqVoteFile::<true>::new_node(domain.clone(), voter),
290 mode!(IFREG, 0o644),
291 );
292 }
293 });
294}
295
296fn normalize_gpu_freq_to_hz(raw_value: u64) -> u64 {
299 if raw_value == 0 {
300 0
301 } else if raw_value < KHZ_TO_HZ_THRESHOLD {
302 raw_value.saturating_mul(1_000)
303 } else {
304 raw_value
305 }
306}
307
308#[derive(Debug)]
309struct GpuPowerDomainState {
310 power_control_on: bool,
312
313 power_generation: u64,
315
316 min_votes: HashMap<&'static str, u64>,
318
319 min_freq_generation: u64,
321
322 max_votes: HashMap<&'static str, u64>,
324
325 max_freq_generation: u64,
327
328 polling_interval_ms: u64,
330
331 provider: Option<Arc<fmagma::PowerElementProviderSynchronousProxy>>,
333
334 power_on_token: Option<zx::Event>,
336
337 active_power_lease: Option<zx::EventPair>,
340
341 min_freq_lease: Option<zx::EventPair>,
343
344 applied_min_freq_hz: Option<u64>,
346
347 max_freq_limit_handle: Option<zx::EventPair>,
350
351 applied_max_freq_hz: Option<u64>,
353}
354
355impl Default for GpuPowerDomainState {
356 fn default() -> Self {
357 Self {
358 power_control_on: false,
359 power_generation: 0,
360 min_votes: HashMap::new(),
361 min_freq_generation: 0,
362 max_votes: HashMap::new(),
363 max_freq_generation: 0,
364 polling_interval_ms: DEFAULT_POLLING_INTERVAL_MS,
365 provider: None,
366 power_on_token: None,
367 active_power_lease: None,
368 min_freq_lease: None,
369 applied_min_freq_hz: None,
370 max_freq_limit_handle: None,
371 applied_max_freq_hz: None,
372 }
373 }
374}
375
376#[derive(Debug, Default)]
383struct GpuPowerDomain {
384 config: GpuDeviceConfig,
385 topology: Option<fpbroker::TopologySynchronousProxy>,
386 state: LockDepMutex<GpuPowerDomainState, MagmaPowerDomainLock>,
387}
388
389impl GpuPowerDomain {
390 fn new(config: GpuDeviceConfig) -> Self {
391 Self {
392 config,
393 topology: connect_to_protocol_sync::<fpbroker::TopologyMarker>().ok(),
394 state: LockDepMutex::default(),
395 }
396 }
397
398 #[cfg(test)]
401 fn with_proxies(
402 provider: fmagma::PowerElementProviderSynchronousProxy,
403 topology: fpbroker::TopologySynchronousProxy,
404 ) -> Self {
405 let domain = Self::with_topology(topology);
406 domain.set_provider(Arc::new(provider));
407 domain
408 }
409
410 #[cfg(test)]
413 fn with_topology(topology: fpbroker::TopologySynchronousProxy) -> Self {
414 Self {
415 config: GpuDeviceConfig::default(),
416 topology: Some(topology),
417 state: LockDepMutex::default(),
418 }
419 }
420
421 fn set_provider(&self, provider: Arc<fmagma::PowerElementProviderSynchronousProxy>) {
425 let power_on_token = match provider.get_power_goals(fidl_deadline()) {
426 Ok(goals) => goals
427 .into_iter()
428 .find(|g| g.type_ == Some(fmagma::PowerGoalType::OnReadyForWork))
429 .and_then(|g| g.token),
430 Err(err) => {
431 log_warn!("PowerElementProvider::GetPowerGoals failed: {:?}", err);
432 return;
433 }
434 };
435
436 let (
437 reconcile_power,
438 reconcile_min_freq,
439 reconcile_max_freq,
440 _dropped_power_lease,
441 _dropped_min_freq_lease,
442 _dropped_max_freq_handle,
443 ) = {
444 let mut state = self.state.lock();
445 state.provider = Some(provider);
446 state.power_on_token = power_on_token;
447 let power_lease = state.active_power_lease.take();
448 let min_freq_lease = state.min_freq_lease.take();
449 let max_freq_handle = state.max_freq_limit_handle.take();
450 state.applied_min_freq_hz = None;
451 state.applied_max_freq_hz = None;
452 state.power_generation = state.power_generation.wrapping_add(1);
453 state.min_freq_generation = state.min_freq_generation.wrapping_add(1);
454 state.max_freq_generation = state.max_freq_generation.wrapping_add(1);
455 (
456 state.power_control_on,
457 !state.min_votes.is_empty(),
458 !state.max_votes.is_empty(),
459 power_lease,
460 min_freq_lease,
461 max_freq_handle,
462 )
463 };
464 if reconcile_power {
465 let _ = self.sync_power_control();
466 }
467 if reconcile_min_freq {
468 let _ = self.sync_min_freq();
469 }
470 if reconcile_max_freq {
471 let _ = self.sync_max_freq();
472 }
473 }
474
475 fn acquire_lease(
476 &self,
477 token: zx::Event,
478 level: fpbroker::PowerLevel,
479 lease_name: &str,
480 ) -> Result<zx::EventPair, ()> {
481 let Some(topology) = self.topology.as_ref() else {
482 return Err(());
483 };
484 let (client_token, broker_token) = zx::EventPair::create();
485 let schema = fpbroker::LeaseSchema {
486 lease_token: Some(broker_token),
487 lease_name: Some(lease_name.to_string()),
488 dependencies: Some(vec![fpbroker::LeaseDependency {
489 requires_token: Some(token),
490 requires_level: Some(level),
491 ..Default::default()
492 }]),
493 should_return_pending_lease: Some(true),
497 ..Default::default()
498 };
499 match topology.lease(schema, fidl_deadline()) {
500 Ok(Ok(())) => Ok(client_token),
501 Ok(Err(err)) => {
502 log_warn!("Power Broker rejected GPU lease {}: {:?}", lease_name, err);
503 Err(())
504 }
505 Err(err) => {
506 log_warn!("Transport error leasing {}: {:?}", lease_name, err);
507 Err(())
508 }
509 }
510 }
511
512 fn set_power_control(&self, on: bool) -> Result<(), Errno> {
513 let _dropped_lease = {
514 let mut state = self.state.lock();
515 state.power_control_on = on;
516 state.power_generation = state.power_generation.wrapping_add(1);
517 if !on { state.active_power_lease.take() } else { None }
518 };
519 if on {
520 self.sync_power_control()?;
521 }
522 Ok(())
523 }
524
525 fn sync_power_control(&self) -> Result<(), Errno> {
526 let (generation, token) = {
527 let state = self.state.lock();
528 if !state.power_control_on || state.active_power_lease.is_some() {
529 return Ok(());
530 }
531 if state.provider.is_none() || self.topology.is_none() {
532 return Ok(());
533 }
534 let Some(ref goal_token) = state.power_on_token else {
535 return Ok(());
536 };
537 let Ok(dup_token) = goal_token.duplicate_handle(zx::Rights::SAME_RIGHTS) else {
538 return error!(EIO);
539 };
540 (state.power_generation, dup_token)
541 };
542
543 let lease_result = self.acquire_lease(token, GPU_POWER_LEVEL_ON, "starnix-gpu-power-on");
544 let mut state = self.state.lock();
545 if state.power_generation != generation || !state.power_control_on {
546 drop(state);
547 return lease_result.map(|_| ()).map_err(|()| errno!(EIO));
548 }
549 if let Ok(lease) = lease_result {
550 let prev = state.active_power_lease.replace(lease);
551 drop(state);
552 drop(prev);
553 return Ok(());
554 }
555 drop(state);
556 error!(EIO)
557 }
558
559 fn power_control_is_on(&self) -> bool {
560 self.state.lock().power_control_on
561 }
562
563 fn update_min_freq_vote(&self, voter: &'static str, raw_value: u64) -> Result<(), Errno> {
564 let hz = normalize_gpu_freq_to_hz(raw_value);
565 let snapped = self.config.snap_min_freq_hz(hz);
566
567 {
568 let mut state = self.state.lock();
569 match snapped {
570 Some(freq_hz) => {
571 state.min_votes.insert(voter, freq_hz);
572 }
573 None => {
574 state.min_votes.remove(voter);
575 }
576 }
577 state.min_freq_generation = state.min_freq_generation.wrapping_add(1);
578 }
579 self.sync_min_freq()
580 }
581
582 fn sync_min_freq(&self) -> Result<(), Errno> {
583 let (generation, target_hz, provider) = {
584 let mut state = self.state.lock();
585 let effective_min_hz = state.min_votes.values().copied().max();
586 if state.applied_min_freq_hz == effective_min_hz {
587 return Ok(());
588 }
589
590 let Some(target_hz) = effective_min_hz else {
591 let dropped = state.min_freq_lease.take();
592 state.applied_min_freq_hz = None;
593 drop(state);
594 drop(dropped);
595 return Ok(());
596 };
597 let (Some(provider), true) =
598 (state.provider.as_ref().cloned(), self.topology.is_some())
599 else {
600 let dropped = state.min_freq_lease.take();
601 state.applied_min_freq_hz = None;
602 drop(state);
603 drop(dropped);
604 return Ok(());
605 };
606 (state.min_freq_generation, target_hz, provider)
607 };
608
609 let req = fmagma::PowerElementProviderGetClockSpeedLevelRequest {
610 hz: Some(target_hz),
611 allow_max: Some(true),
612 ..Default::default()
613 };
614 let mut reset_provider = false;
618 let mut not_supported = false;
619 let mut sync_result = Ok(());
620 let new_lease = match provider.get_clock_speed_level(&req, fidl_deadline()) {
621 Ok(Ok(resp)) => {
622 if let (Some(token), Some(level)) = (resp.token, resp.level) {
623 match self.acquire_lease(token, level, "starnix-gpu-min-freq") {
624 Ok(lease) => Some(lease),
625 Err(()) => {
626 sync_result = error!(EIO);
627 None
628 }
629 }
630 } else {
631 sync_result = error!(EIO);
632 None
633 }
634 }
635 Ok(Err(status)) => {
636 let zx_status = zx::Status::err_from_raw(status);
637 log_warn!("GetClockSpeedLevel({}) returned status {}", target_hz, zx_status);
638 if zx_status == zx::Status::NOT_SUPPORTED {
639 not_supported = true;
640 } else {
641 sync_result = error!(EIO);
642 }
643 None
644 }
645 Err(err) => {
646 log_warn!("GetClockSpeedLevel transport error: {:?}", err);
647 reset_provider = true;
648 sync_result = error!(EIO);
649 None
650 }
651 };
652
653 let _dropped_lease = {
654 let mut state = self.state.lock();
655 if reset_provider && state.provider.as_ref().is_some_and(|p| Arc::ptr_eq(p, &provider))
656 {
657 state.power_on_token = None;
658 state.provider = None;
659 }
660 if state.min_freq_generation != generation {
661 new_lease
662 } else if let Some(lease) = new_lease {
663 state.applied_min_freq_hz = Some(target_hz);
664 state.min_freq_lease.replace(lease)
665 } else if not_supported {
666 state.applied_min_freq_hz = Some(target_hz);
667 state.min_freq_lease.take()
668 } else if state.applied_min_freq_hz != Some(target_hz) {
669 state.applied_min_freq_hz = None;
670 state.min_freq_lease.take()
671 } else {
672 None
673 }
674 };
675 sync_result
676 }
677
678 fn min_voter_freq_hz(&self, voter: &'static str) -> u64 {
679 let state = self.state.lock();
680 if voter == "min_freq" {
681 state.min_votes.values().copied().max().unwrap_or(0)
682 } else {
683 state.min_votes.get(voter).copied().unwrap_or(0)
684 }
685 }
686
687 fn update_max_freq_vote(&self, voter: &'static str, raw_value: u64) -> Result<(), Errno> {
688 let hz = normalize_gpu_freq_to_hz(raw_value);
689 let snapped = self.config.snap_max_freq_hz(hz);
690 {
691 let mut state = self.state.lock();
692 match snapped {
693 Some(freq_hz) => {
694 state.max_votes.insert(voter, freq_hz);
695 }
696 None => {
697 state.max_votes.remove(voter);
698 }
699 }
700 state.max_freq_generation = state.max_freq_generation.wrapping_add(1);
701 }
702 self.sync_max_freq()
703 }
704
705 fn sync_max_freq(&self) -> Result<(), Errno> {
706 let (generation, target_hz, provider) = {
707 let mut state = self.state.lock();
708 let effective_max_hz = state.max_votes.values().copied().min();
709 if state.applied_max_freq_hz == effective_max_hz {
710 return Ok(());
711 }
712
713 let Some(target_hz) = effective_max_hz else {
714 let dropped = state.max_freq_limit_handle.take();
715 state.applied_max_freq_hz = None;
716 drop(state);
717 drop(dropped);
718 return Ok(());
719 };
720 let Some(provider) = state.provider.as_ref().cloned() else {
721 let dropped = state.max_freq_limit_handle.take();
722 state.applied_max_freq_hz = None;
723 drop(state);
724 drop(dropped);
725 return Ok(());
726 };
727 (state.max_freq_generation, target_hz, provider)
728 };
729
730 let req = fmagma::PowerElementProviderSetClockLimitRequest {
731 hz: Some(target_hz),
732 ..Default::default()
733 };
734 let mut reset_provider = false;
735 let mut not_supported = false;
736 let mut sync_result = Ok(());
737 let new_handle = match provider.set_clock_limit(&req, fidl_deadline()) {
738 Ok(Ok(resp)) => {
739 if let Some(handle) = resp.handle {
740 Some(handle)
741 } else {
742 sync_result = error!(EIO);
743 None
744 }
745 }
746 Ok(Err(status)) => {
747 let zx_status = zx::Status::err_from_raw(status);
748 log_warn!("SetClockLimit({}) returned status {}", target_hz, zx_status);
749 if zx_status == zx::Status::NOT_SUPPORTED {
750 not_supported = true;
751 } else {
752 sync_result = error!(EIO);
753 }
754 None
755 }
756 Err(err) => {
757 log_warn!("SetClockLimit transport error: {:?}", err);
758 reset_provider = true;
759 sync_result = error!(EIO);
760 None
761 }
762 };
763
764 let _dropped_handle = {
765 let mut state = self.state.lock();
766 if reset_provider && state.provider.as_ref().is_some_and(|p| Arc::ptr_eq(p, &provider))
767 {
768 state.power_on_token = None;
769 state.provider = None;
770 }
771 if state.max_freq_generation != generation {
772 new_handle
773 } else if let Some(handle) = new_handle {
774 state.applied_max_freq_hz = Some(target_hz);
775 state.max_freq_limit_handle.replace(handle)
776 } else if not_supported {
777 state.applied_max_freq_hz = Some(target_hz);
778 state.max_freq_limit_handle.take()
779 } else if state.applied_max_freq_hz != Some(target_hz) {
780 state.applied_max_freq_hz = None;
781 state.max_freq_limit_handle.take()
782 } else {
783 None
784 }
785 };
786 sync_result
787 }
788
789 fn max_voter_freq_hz(&self, voter: &'static str) -> u64 {
790 let max_hz = self.config.max_freq_hz();
791 let state = self.state.lock();
792 if voter == "max_freq" {
793 state.max_votes.values().copied().min().unwrap_or(max_hz)
794 } else {
795 state.max_votes.get(voter).copied().unwrap_or(max_hz)
796 }
797 }
798
799 fn cur_freq_hz(&self) -> u64 {
800 let default_hz = self.config.default_freq_hz;
801 let max_hz = self.config.max_freq_hz();
802 let state = self.state.lock();
803 let floor = state.min_votes.values().copied().max().unwrap_or(default_hz);
804 let ceil = state.max_votes.values().copied().min().unwrap_or(max_hz);
805 floor.max(default_hz).min(ceil)
806 }
807
808 fn set_polling_interval_ms(&self, ms: u64) {
809 self.state.lock().polling_interval_ms = ms;
810 }
811
812 fn polling_interval_ms(&self) -> u64 {
813 self.state.lock().polling_interval_ms
814 }
815}
816
817struct GpuPowerControlFile {
818 domain: Arc<GpuPowerDomain>,
819}
820
821impl GpuPowerControlFile {
822 fn new_node(domain: Arc<GpuPowerDomain>) -> impl FsNodeOps {
823 BytesFile::new_node(Self { domain })
824 }
825}
826
827impl BytesFileOps for GpuPowerControlFile {
828 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
829 let s: &[u8] = if self.domain.power_control_is_on() { b"on\n" } else { b"auto\n" };
830 Ok(s.into())
831 }
832
833 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
834 let s = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?.trim();
835 match s {
836 "on" => self.domain.set_power_control(true),
837 "auto" => self.domain.set_power_control(false),
838 _ => error!(EINVAL),
839 }
840 }
841}
842
843struct GpuRuntimeStatusFile {
844 domain: Arc<GpuPowerDomain>,
845}
846
847impl GpuRuntimeStatusFile {
848 fn new_node(domain: Arc<GpuPowerDomain>) -> impl FsNodeOps {
849 BytesFile::new_node(Self { domain })
850 }
851}
852
853impl BytesFileOps for GpuRuntimeStatusFile {
854 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
855 let s: &[u8] = if self.domain.power_control_is_on() { b"active\n" } else { b"suspended\n" };
856 Ok(s.into())
857 }
858}
859
860struct GpuFreqVoteFile<const IS_MAX: bool> {
861 domain: Arc<GpuPowerDomain>,
862 voter: &'static str,
863}
864
865impl<const IS_MAX: bool> GpuFreqVoteFile<IS_MAX> {
866 fn new_node(domain: Arc<GpuPowerDomain>, voter: &'static str) -> impl FsNodeOps {
867 BytesFile::new_node(Self { domain, voter })
868 }
869}
870
871impl<const IS_MAX: bool> BytesFileOps for GpuFreqVoteFile<IS_MAX> {
872 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
873 let freq_hz = if IS_MAX {
874 self.domain.max_voter_freq_hz(self.voter)
875 } else {
876 self.domain.min_voter_freq_hz(self.voter)
877 };
878 Ok(format!("{freq_hz}\n").into_bytes().into())
879 }
880
881 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
882 let val = parse_unsigned_file::<u64>(&data)?;
883 if IS_MAX {
884 self.domain.update_max_freq_vote(self.voter, val)
885 } else {
886 self.domain.update_min_freq_vote(self.voter, val)
887 }
888 }
889}
890
891struct GpuCurFreqFile {
892 domain: Arc<GpuPowerDomain>,
893}
894
895impl GpuCurFreqFile {
896 fn new_node(domain: Arc<GpuPowerDomain>) -> impl FsNodeOps {
897 BytesFile::new_node(Self { domain })
898 }
899}
900
901impl BytesFileOps for GpuCurFreqFile {
902 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
903 Ok(format!("{}\n", self.domain.cur_freq_hz()).into_bytes().into())
904 }
905}
906
907struct GpuPollingIntervalFile {
908 domain: Arc<GpuPowerDomain>,
909}
910
911impl GpuPollingIntervalFile {
912 fn new_node(domain: Arc<GpuPowerDomain>) -> impl FsNodeOps {
913 BytesFile::new_node(Self { domain })
914 }
915}
916
917impl BytesFileOps for GpuPollingIntervalFile {
918 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
919 Ok(format!("{}\n", self.domain.polling_interval_ms()).into_bytes().into())
920 }
921
922 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
923 let ms = parse_unsigned_file::<u64>(&data)?;
924 self.domain.set_polling_interval_ms(ms);
925 Ok(())
926 }
927}
928
929#[cfg(test)]
930mod tests {
931 use super::*;
932 use fidl::endpoints::create_endpoints;
933 use fidl_fuchsia_io as fio;
934 use fuchsia_component::server::ServiceFs;
935 use futures::StreamExt;
936 use starnix_core::testing::spawn_kernel_and_run;
937 use starnix_sync::Mutex;
938 use std::sync::atomic::{AtomicU64, Ordering};
939
940 #[::fuchsia::test]
941 async fn sysfs_registers_gpu_devfreq_and_genpd_nodes() {
942 spawn_kernel_and_run(async |current_task| {
943 let kernel = current_task.kernel();
944 register_gpu_devices(kernel, GpuDeviceConfig::default());
945 let root = &kernel.device_registry.objects.root;
946
947 assert!(root.lookup("devices/genpd:0:34f00000.gpu0/power/control".into()).is_some());
948 assert!(
949 root.lookup("devices/genpd:0:34f00000.gpu0/power/runtime_status".into()).is_some()
950 );
951 assert!(root.lookup("class/devfreq/34f00000.gpu0".into()).is_some());
952 assert!(root.lookup("bus/platform/devices/34f00000.gpu0".into()).is_some());
953 assert!(root.lookup("devices/platform/34f00000.gpu0/power/control".into()).is_some());
954 assert!(
955 root.lookup(
956 "devices/platform/34f00000.gpu0/devfreq/34f00000.gpu0/vote_manager/powerhint_min_freq"
957 .into()
958 )
959 .is_some()
960 );
961 assert!(
962 root.lookup("devices/platform/34f00000.gpu0/devfreq/34f00000.gpu0/min_freq".into())
963 .is_some()
964 );
965 assert!(
966 root.lookup("devices/platform/34f00000.gpu0/devfreq/34f00000.gpu0/max_freq".into())
967 .is_some()
968 );
969 assert!(
970 root.lookup("devices/platform/34f00000.gpu0/devfreq/34f00000.gpu0/cur_freq".into())
971 .is_some()
972 );
973 assert!(
974 root.lookup(
975 "devices/platform/34f00000.gpu0/devfreq/34f00000.gpu0/available_frequencies"
976 .into()
977 )
978 .is_some()
979 );
980 assert!(
981 root.lookup(
982 "devices/platform/34f00000.gpu0/devfreq/34f00000.gpu0/polling_interval".into()
983 )
984 .is_some()
985 );
986 })
987 .await;
988 }
989
990 struct MockPowerBackend {
991 power_token_koid: zx::Koid,
992 clock_token_koid: zx::Koid,
993 power_token: zx::Event,
994 clock_token: zx::Event,
995 leased_power_koid: Arc<AtomicU64>,
996 leased_clock_koid: Arc<AtomicU64>,
997 leased_clock_level: Arc<AtomicU64>,
998 capped_clock_hz: Arc<AtomicU64>,
999 clock_speed_calls: Arc<AtomicU64>,
1000 active_broker_tokens: Arc<Mutex<Vec<zx::EventPair>>>,
1001 active_limit_tokens: Arc<Mutex<Vec<zx::EventPair>>>,
1002 }
1003
1004 impl MockPowerBackend {
1005 fn new() -> Self {
1006 let power_token = zx::Event::create();
1007 let power_token_koid = power_token.koid().expect("koid");
1008 let clock_token = zx::Event::create();
1009 let clock_token_koid = clock_token.koid().expect("koid");
1010 Self {
1011 power_token_koid,
1012 clock_token_koid,
1013 power_token,
1014 clock_token,
1015 leased_power_koid: Arc::new(AtomicU64::new(0)),
1016 leased_clock_koid: Arc::new(AtomicU64::new(0)),
1017 leased_clock_level: Arc::new(AtomicU64::new(0)),
1018 capped_clock_hz: Arc::new(AtomicU64::new(0)),
1019 clock_speed_calls: Arc::new(AtomicU64::new(0)),
1020 active_broker_tokens: Arc::new(Mutex::new(Vec::new())),
1021 active_limit_tokens: Arc::new(Mutex::new(Vec::new())),
1022 }
1023 }
1024
1025 fn spawn_provider(
1026 &self,
1027 scope: &fasync::Scope,
1028 mut stream: fmagma::PowerElementProviderRequestStream,
1029 reject_576mhz_not_supported: bool,
1030 ) {
1031 let power_token =
1032 self.power_token.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup");
1033 let clock_token =
1034 self.clock_token.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup");
1035 let capped_clock_recorder = self.capped_clock_hz.clone();
1036 let clock_speed_calls = self.clock_speed_calls.clone();
1037 let limit_tokens_holder = self.active_limit_tokens.clone();
1038 scope.spawn(async move {
1039 while let Some(Ok(req)) = stream.next().await {
1040 match req {
1041 fmagma::PowerElementProviderRequest::GetPowerGoals { responder } => {
1042 let dup =
1043 power_token.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup");
1044 let _ = responder.send(vec![fmagma::PowerGoal {
1045 type_: Some(fmagma::PowerGoalType::OnReadyForWork),
1046 token: Some(dup),
1047 ..Default::default()
1048 }]);
1049 }
1050 fmagma::PowerElementProviderRequest::GetClockSpeedLevel {
1051 payload,
1052 responder,
1053 } => {
1054 clock_speed_calls.fetch_add(1, Ordering::SeqCst);
1055 if reject_576mhz_not_supported && payload.hz == Some(576_000_000) {
1056 let _ = responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()));
1057 } else {
1058 let dup = clock_token
1059 .duplicate_handle(zx::Rights::SAME_RIGHTS)
1060 .expect("dup");
1061 let level = if payload.hz == Some(576_000_000) { 6u8 } else { 3u8 };
1062 let _ = responder.send(Ok(
1063 fmagma::PowerElementProviderGetClockSpeedLevelResponse {
1064 token: Some(dup),
1065 level: Some(level),
1066 actual_hz: payload.hz,
1067 ..Default::default()
1068 },
1069 ));
1070 }
1071 }
1072 fmagma::PowerElementProviderRequest::SetClockLimit {
1073 payload,
1074 responder,
1075 } => {
1076 if reject_576mhz_not_supported && payload.hz == Some(576_000_000) {
1077 let _ = responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()));
1078 } else if reject_576mhz_not_supported && payload.hz == Some(396_000_000)
1079 {
1080 let _ = responder.send(Err(zx::Status::INTERNAL.into_raw()));
1081 } else {
1082 let (client_ep, server_ep) = zx::EventPair::create();
1083 capped_clock_recorder
1084 .store(payload.hz.unwrap_or(0), Ordering::SeqCst);
1085 limit_tokens_holder.lock().push(server_ep);
1086 let _ = responder.send(Ok(
1087 fmagma::PowerElementProviderSetClockLimitResponse {
1088 handle: Some(client_ep),
1089 actual_hz: payload.hz,
1090 ..Default::default()
1091 },
1092 ));
1093 }
1094 }
1095 _ => {}
1096 }
1097 }
1098 });
1099 }
1100
1101 fn spawn_topology(
1102 &self,
1103 scope: &fasync::Scope,
1104 topology_server: fidl::endpoints::ServerEnd<fpbroker::TopologyMarker>,
1105 reject_leases: bool,
1106 ) {
1107 let power_token_raw = self.power_token_koid.raw_koid();
1108 let power_koid_recorder = self.leased_power_koid.clone();
1109 let clock_koid_recorder = self.leased_clock_koid.clone();
1110 let clock_level_recorder = self.leased_clock_level.clone();
1111 let tokens_holder = self.active_broker_tokens.clone();
1112 scope.spawn(async move {
1113 let mut stream = topology_server.into_stream();
1114 while let Some(Ok(req)) = stream.next().await {
1115 if let fpbroker::TopologyRequest::Lease { payload, responder } = req {
1116 assert_eq!(payload.should_return_pending_lease, Some(true));
1117 if reject_leases {
1118 let _ = responder.send(Err(fpbroker::LeaseError::Internal));
1119 continue;
1120 }
1121 if let Some(deps) = payload.dependencies {
1122 for dep in deps {
1123 let koid = dep
1124 .requires_token
1125 .as_ref()
1126 .and_then(|t| t.koid().ok())
1127 .map(|k| k.raw_koid())
1128 .unwrap_or(0);
1129 if koid == power_token_raw {
1130 power_koid_recorder.store(koid, Ordering::SeqCst);
1131 } else {
1132 clock_koid_recorder.store(koid, Ordering::SeqCst);
1133 clock_level_recorder.store(
1134 dep.requires_level.unwrap_or(GPU_POWER_LEVEL_OFF) as u64,
1135 Ordering::SeqCst,
1136 );
1137 }
1138 }
1139 }
1140 if let Some(lt) = payload.lease_token {
1141 tokens_holder.lock().push(lt);
1142 }
1143 let _ = responder.send(Ok(()));
1144 }
1145 }
1146 });
1147 }
1148 }
1149
1150 #[::fuchsia::test]
1151 async fn gpu_power_domain_leases_power_broker_on_hints() {
1152 let (provider_client, provider_server) =
1153 create_endpoints::<fmagma::PowerElementProviderMarker>();
1154 let (topology_client, topology_server) = create_endpoints::<fpbroker::TopologyMarker>();
1155
1156 let backend = MockPowerBackend::new();
1157 let scope = fasync::Scope::new();
1158 backend.spawn_provider(&scope, provider_server.into_stream(), false);
1159 backend.spawn_topology(&scope, topology_server, false);
1160
1161 let domain = fasync::unblock(move || {
1162 let domain = GpuPowerDomain::with_proxies(
1163 fmagma::PowerElementProviderSynchronousProxy::new(provider_client.into_channel()),
1164 fpbroker::TopologySynchronousProxy::new(topology_client.into_channel()),
1165 );
1166
1167 domain.set_power_control(true).expect("set power control on");
1169 assert!(domain.power_control_is_on());
1170 domain.update_min_freq_vote("powerhint_min_freq", 576_000).expect("vote 576000 kHz");
1171 assert_eq!(domain.min_voter_freq_hz("min_freq"), 576_000_000);
1172 assert_eq!(domain.cur_freq_hz(), 576_000_000);
1173
1174 domain.update_min_freq_vote("powerhint_min_freq", 0).expect("clear min_freq");
1176 assert_eq!(domain.min_voter_freq_hz("min_freq"), 0);
1177 assert_eq!(domain.cur_freq_hz(), 396_000_000);
1178 domain.update_max_freq_vote("powerhint_max_freq", 300_000).expect("cap max_freq");
1179 assert_eq!(domain.max_voter_freq_hz("max_freq"), 300_000_000);
1180 assert_eq!(domain.cur_freq_hz(), 300_000_000);
1181 domain.update_max_freq_vote("powerhint_max_freq", 0).expect("clear max_freq with 0");
1182 assert_eq!(domain.max_voter_freq_hz("max_freq"), 1_094_000_000);
1183 assert_eq!(domain.cur_freq_hz(), 396_000_000);
1184 domain.set_power_control(false).expect("set power control auto");
1185 assert!(!domain.power_control_is_on());
1186 domain
1187 })
1188 .await;
1189
1190 assert_eq!(
1191 backend.leased_power_koid.load(Ordering::SeqCst),
1192 backend.power_token_koid.raw_koid()
1193 );
1194 assert_eq!(
1195 backend.leased_clock_koid.load(Ordering::SeqCst),
1196 backend.clock_token_koid.raw_koid()
1197 );
1198 assert_eq!(backend.leased_clock_level.load(Ordering::SeqCst), 6);
1199 assert_eq!(backend.capped_clock_hz.load(Ordering::SeqCst), 300_000_000);
1200
1201 let broker_tokens = backend.active_broker_tokens.lock();
1203 assert_eq!(broker_tokens.len(), 2);
1204 for bt in broker_tokens.iter() {
1205 let signals = bt
1206 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST);
1207 assert!(signals.is_ok());
1208 }
1209
1210 let limit_tokens = backend.active_limit_tokens.lock();
1212 assert_eq!(limit_tokens.len(), 1);
1213 for lt in limit_tokens.iter() {
1214 let signals = lt
1215 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST);
1216 assert!(signals.is_ok());
1217 }
1218 drop(domain);
1219 }
1220
1221 #[::fuchsia::test]
1222 async fn watch_power_service_reconciles_active_votes_on_provider_arrival() {
1223 let (topology_client, topology_server) = create_endpoints::<fpbroker::TopologyMarker>();
1224 let backend = Arc::new(MockPowerBackend::new());
1225 let scope = fasync::Scope::new();
1226 backend.spawn_topology(&scope, topology_server, false);
1227
1228 let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
1229 let mut fs = ServiceFs::new();
1230 fs.add_fidl_service_instance("default", |req: fmagma::PowerServiceRequest| req);
1231 fs.serve_connection(dir_server).expect("serve PowerService directory");
1232 let backend_for_fs = Arc::clone(&backend);
1233 scope.spawn(async move {
1234 let inner_scope = fasync::Scope::new();
1235 while let Some(fmagma::PowerServiceRequest::PowerElementProvider(stream)) =
1236 fs.next().await
1237 {
1238 backend_for_fs.spawn_provider(&inner_scope, stream, false);
1239 }
1240 });
1241
1242 let domain = Arc::new(GpuPowerDomain::with_topology(
1243 fpbroker::TopologySynchronousProxy::new(topology_client.into_channel()),
1244 ));
1245
1246 domain.set_power_control(true).expect("power control before provider");
1248 domain.update_min_freq_vote("powerhint_min_freq", 576_000).expect("vote before provider");
1249 assert_eq!(backend.leased_power_koid.load(Ordering::SeqCst), 0);
1250 assert_eq!(backend.leased_clock_koid.load(Ordering::SeqCst), 0);
1251
1252 let service =
1255 fclient::Service::open_from_dir(dir_client, fmagma::PowerServiceMarker).expect("open");
1256 let watcher_domain = Arc::clone(&domain);
1257 scope.spawn(async move {
1258 let _ = watch_power_service(watcher_domain, service).await;
1259 });
1260
1261 while backend.leased_power_koid.load(Ordering::SeqCst) == 0
1262 || backend.leased_clock_koid.load(Ordering::SeqCst) == 0
1263 {
1264 fasync::Timer::new(zx::MonotonicDuration::from_millis(10)).await;
1265 }
1266
1267 assert_eq!(
1268 backend.leased_power_koid.load(Ordering::SeqCst),
1269 backend.power_token_koid.raw_koid()
1270 );
1271 assert_eq!(
1272 backend.leased_clock_koid.load(Ordering::SeqCst),
1273 backend.clock_token_koid.raw_koid()
1274 );
1275 assert_eq!(backend.leased_clock_level.load(Ordering::SeqCst), 6);
1276 }
1277
1278 #[::fuchsia::test]
1279 async fn gpu_power_domain_returns_eio_on_lease_error_and_allows_not_supported() {
1280 let (provider_client, provider_server) =
1281 create_endpoints::<fmagma::PowerElementProviderMarker>();
1282 let (topology_client, topology_server) = create_endpoints::<fpbroker::TopologyMarker>();
1283
1284 let backend = MockPowerBackend::new();
1285 let scope = fasync::Scope::new();
1286 backend.spawn_provider(&scope, provider_server.into_stream(), true);
1287 backend.spawn_topology(&scope, topology_server, true);
1288
1289 fasync::unblock(move || {
1290 let domain = GpuPowerDomain::with_proxies(
1291 fmagma::PowerElementProviderSynchronousProxy::new(provider_client.into_channel()),
1292 fpbroker::TopologySynchronousProxy::new(topology_client.into_channel()),
1293 );
1294
1295 assert_eq!(domain.set_power_control(true), error!(EIO));
1297
1298 assert_eq!(domain.update_min_freq_vote("powerhint_min_freq", 576_000), Ok(()));
1302 assert_eq!(domain.update_min_freq_vote("soft_min_freq", 576_000), Ok(()));
1303 assert_eq!(domain.update_min_freq_vote("soft_min_freq", 0), Ok(()));
1304
1305 assert_eq!(domain.update_min_freq_vote("powerhint_min_freq", 396_000), error!(EIO));
1307
1308 assert_eq!(domain.update_max_freq_vote("powerhint_max_freq", 576_000), Ok(()));
1311 assert_eq!(domain.update_max_freq_vote("powerhint_max_freq", 396_000), error!(EIO));
1312 })
1313 .await;
1314 assert_eq!(backend.clock_speed_calls.load(Ordering::SeqCst), 2);
1317 }
1318}