fdf_power/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::future::Future;
6use fdf_component::{Driver, DriverContext};
7use futures::TryStreamExt;
8use log::{error, warn};
9use std::sync::{Arc, Weak};
10use zx::Status;
11use {fidl_fuchsia_power_system as fpower, fuchsia_async as fasync};
12
13/// Implement this trait if you'd like to get notifications when the system is about to go into
14/// suspend and come out of resume.
15pub trait SuspendableDriver: Driver {
16    /// Called prior the system entering suspend. The system is not guaranteed to enter suspend,
17    /// but `resume` will be called regardless before a subsequent suspension attempt occurs.
18    fn suspend(&self) -> impl Future<Output = ()> + Send;
19
20    /// Called after `suspend` to indicate the system is no longer in suspend. The system may not
21    /// have actually entered suspension in between `suspend` and `resume` invocations.
22    fn resume(&self) -> impl Future<Output = ()> + Send;
23
24    /// Returns whether or not suspend is enabled. If false is returned, suspend and resume methods
25    /// will never be called.
26    fn suspend_enabled(&self) -> bool;
27}
28
29/// Wrapper trait to indicate the driver supports power operations.
30pub struct Suspendable<T: Driver> {
31    #[allow(unused)]
32    scope: fasync::Scope,
33    driver: Arc<T>,
34}
35
36async fn run_suspend_blocker<T: SuspendableDriver>(
37    driver: Weak<T>,
38    mut service: fpower::SuspendBlockerRequestStream,
39) {
40    use fpower::SuspendBlockerRequest::*;
41    while let Some(req) = service.try_next().await.unwrap() {
42        match req {
43            BeforeSuspend { responder, .. } => {
44                if let Some(driver) = driver.upgrade() {
45                    driver.suspend().await;
46                } else {
47                    return;
48                }
49                responder.send()
50            }
51            AfterResume { responder, .. } => {
52                if let Some(driver) = driver.upgrade() {
53                    driver.resume().await;
54                } else {
55                    return;
56                }
57                responder.send()
58            }
59            // Ignore unknown requests.
60            _ => {
61                warn!("Received unknown sag listener request");
62                Ok(())
63            }
64        }
65        .unwrap()
66    }
67}
68
69impl<T: SuspendableDriver + Send + Sync> Driver for Suspendable<T> {
70    const NAME: &str = T::NAME;
71
72    async fn start(context: DriverContext) -> Result<Self, Status> {
73        let sag: fpower::ActivityGovernorProxy =
74            context.incoming.connect_protocol().map_err(|err| {
75                error!("Error connecting to sag: {err}");
76                Status::INTERNAL
77            })?;
78
79        let driver = Arc::new(T::start(context).await?);
80
81        let scope = fasync::Scope::new_with_name("suspend");
82        if driver.suspend_enabled() {
83            let (client, server) = fidl::endpoints::create_endpoints();
84
85            let _ = sag
86                .register_suspend_blocker(fpower::ActivityGovernorRegisterSuspendBlockerRequest {
87                    suspend_blocker: Some(client),
88                    name: Some(Self::NAME.into()),
89                    ..Default::default()
90                })
91                .await
92                .map_err(|err| {
93                    error!("Error connecting to sag: {err}");
94                    Status::INTERNAL
95                })?
96                .map_err(|err| {
97                    error!("Error connecting to sag: {err:?}");
98                    Status::INTERNAL
99                })?;
100
101            let weak_driver = Arc::downgrade(&driver);
102            scope
103                .spawn(async move { run_suspend_blocker(weak_driver, server.into_stream()).await });
104        }
105
106        Ok(Self { driver, scope })
107    }
108
109    async fn stop(&self) {
110        self.driver.stop().await;
111    }
112}