Skip to main content

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 fidl_fuchsia_hardware_power as fhw_power;
8use fidl_fuchsia_power_broker as fpower_broker;
9use fidl_fuchsia_power_system as fpower;
10use fuchsia_async as fasync;
11use futures::TryStreamExt;
12use log::{error, warn};
13use std::sync::{Arc, Weak};
14use zx::Status;
15
16use fidl_next as _;
17
18/// Implement this trait if you'd like to get notifications when the system is about to go into
19/// suspend and come out of resume.
20pub trait SuspendableDriver: Driver {
21    /// Called prior the system entering suspend. The system is not guaranteed to enter suspend,
22    /// but `resume` will be called regardless before a subsequent suspension attempt occurs.
23    fn suspend(&self) -> impl Future<Output = ()> + Send;
24
25    /// Called after `suspend` to indicate the system is no longer in suspend. The system may not
26    /// have actually entered suspension in between `suspend` and `resume` invocations.
27    fn resume(&self) -> impl Future<Output = ()> + Send;
28
29    /// Returns whether or not suspend is enabled. If false is returned, suspend and resume methods
30    /// will never be called.
31    fn suspend_enabled(&self) -> bool;
32}
33
34/// Wrapper trait to indicate the driver supports power operations.
35pub struct Suspendable<T: Driver> {
36    #[expect(unused)]
37    scope: Option<fasync::Scope>,
38    driver: Arc<T>,
39}
40
41async fn run_suspend_blocker<T: SuspendableDriver>(
42    driver: Weak<T>,
43    mut service: fpower::SuspendBlockerRequestStream,
44) {
45    use fpower::SuspendBlockerRequest::*;
46    while let Some(req) = service.try_next().await.unwrap() {
47        match req {
48            BeforeSuspend { responder, .. } => {
49                if let Some(driver) = driver.upgrade() {
50                    driver.suspend().await;
51                } else {
52                    return;
53                }
54                let _ = responder.send();
55            }
56            AfterResume { responder, .. } => {
57                if let Some(driver) = driver.upgrade() {
58                    driver.resume().await;
59                } else {
60                    return;
61                }
62                let _ = responder.send();
63            }
64            // Ignore unknown requests.
65            _ => {
66                warn!("Received unknown sag listener request");
67            }
68        }
69    }
70}
71
72async fn run_element_runner<T: SuspendableDriver>(
73    driver: Weak<T>,
74    mut service: fpower_broker::ElementRunnerRequestStream,
75) {
76    let mut first_activation_occurred = false;
77    while let Some(req) = service.try_next().await.unwrap_or_default() {
78        if let fpower_broker::ElementRunnerRequest::SetLevel { level, responder } = req {
79            if level != fhw_power::FrameworkElementLevels::Off.into_primitive() as u8 {
80                first_activation_occurred = true;
81                if let Some(driver) = driver.upgrade() {
82                    driver.resume().await;
83                } else {
84                    return;
85                }
86            } else if first_activation_occurred {
87                if let Some(driver) = driver.upgrade() {
88                    driver.suspend().await;
89                } else {
90                    return;
91                }
92            }
93            let _ = responder.send();
94        }
95    }
96}
97
98impl<T: SuspendableDriver + Send + Sync> Driver for Suspendable<T> {
99    const NAME: &str = T::NAME;
100
101    async fn start(mut context: DriverContext) -> Result<Self, Status> {
102        let mut runner = context
103            .start_args
104            .power_element_args
105            .as_mut()
106            .and_then(|args| args.runner_server.take());
107
108        let mut sag = None;
109        if runner.is_none() {
110            sag = Some(
111                context.incoming.connect_protocol::<fpower::ActivityGovernorProxy>().map_err(
112                    |err| {
113                        error!("Error connecting to sag: {err}");
114                        Status::INTERNAL
115                    },
116                )?,
117            );
118        }
119
120        let driver = Arc::new(T::start(context).await?);
121
122        let scope = if driver.suspend_enabled() {
123            let scope = fasync::Scope::new_with_name("suspend");
124            if let Some(runner) = runner.take() {
125                let weak_driver = Arc::downgrade(&driver);
126                scope.spawn(
127                    async move { run_element_runner(weak_driver, runner.into_stream()).await },
128                );
129            } else if let Some(sag) = sag.take() {
130                let (client, server) = fidl::endpoints::create_endpoints();
131
132                let _ = sag
133                    .register_suspend_blocker(
134                        fpower::ActivityGovernorRegisterSuspendBlockerRequest {
135                            suspend_blocker: Some(client),
136                            name: Some(Self::NAME.into()),
137                            ..Default::default()
138                        },
139                    )
140                    .await
141                    .map_err(|err| {
142                        error!("Error connecting to sag: {err}");
143                        Status::INTERNAL
144                    })?
145                    .map_err(|err| {
146                        error!("Error connecting to sag: {err:?}");
147                        Status::INTERNAL
148                    })?;
149
150                let weak_driver = Arc::downgrade(&driver);
151                scope.spawn(
152                    async move { run_suspend_blocker(weak_driver, server.into_stream()).await },
153                );
154            }
155            Some(scope)
156        } else {
157            None
158        };
159
160        Ok(Self { driver, scope })
161    }
162
163    async fn stop(&self) {
164        self.driver.stop().await;
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use fdf_component::testing::harness::TestHarness;
172    use fidl_fuchsia_driver_framework as fdf;
173    use std::sync::atomic::{AtomicBool, Ordering};
174
175    struct TestDriver {
176        suspend_called: Arc<AtomicBool>,
177        resume_called: Arc<AtomicBool>,
178        suspend_enabled: bool,
179        stop_called: Arc<AtomicBool>,
180    }
181
182    impl Driver for TestDriver {
183        const NAME: &str = "test_driver";
184
185        async fn start(_context: DriverContext) -> Result<Self, Status> {
186            Ok(Self {
187                suspend_called: Arc::new(AtomicBool::new(false)),
188                resume_called: Arc::new(AtomicBool::new(false)),
189                suspend_enabled: true,
190                stop_called: Arc::new(AtomicBool::new(false)),
191            })
192        }
193
194        async fn stop(&self) {
195            self.stop_called.store(true, Ordering::SeqCst);
196        }
197    }
198
199    impl SuspendableDriver for TestDriver {
200        async fn suspend(&self) {
201            self.suspend_called.store(true, Ordering::SeqCst);
202        }
203
204        async fn resume(&self) {
205            self.resume_called.store(true, Ordering::SeqCst);
206        }
207
208        fn suspend_enabled(&self) -> bool {
209            self.suspend_enabled
210        }
211    }
212
213    #[fuchsia::test]
214    async fn test_suspend_resume_with_runner() {
215        let (runner_client, runner_server) =
216            fidl::endpoints::create_endpoints::<fpower_broker::ElementRunnerMarker>();
217
218        let mut harness = TestHarness::<Suspendable<TestDriver>>::new().set_power_element_args(
219            fdf::PowerElementArgs { runner_server: Some(runner_server), ..Default::default() },
220        );
221
222        let driver_under_test = harness.start_driver().await.expect("Failed to start driver");
223        let (test_driver_stop_called, test_driver_resume_called, test_driver_suspend_called) = {
224            let suspendable = driver_under_test.get_driver().expect("Failed to get driver");
225            (
226                suspendable.driver.stop_called.clone(),
227                suspendable.driver.resume_called.clone(),
228                suspendable.driver.suspend_called.clone(),
229            )
230        };
231
232        let runner_proxy = runner_client.into_proxy();
233
234        // Level 1 should trigger resume
235        runner_proxy.set_level(1).await.expect("Failed to set level");
236        assert!(test_driver_resume_called.load(Ordering::SeqCst));
237        test_driver_resume_called.store(false, Ordering::SeqCst);
238
239        // Level 0 should trigger suspend
240        runner_proxy.set_level(0).await.expect("Failed to set level");
241        assert!(test_driver_suspend_called.load(Ordering::SeqCst));
242        test_driver_suspend_called.store(false, Ordering::SeqCst);
243
244        // Level 1 again should trigger resume
245        runner_proxy.set_level(1).await.expect("Failed to set level");
246        assert!(test_driver_resume_called.load(Ordering::SeqCst));
247
248        driver_under_test.stop_driver().await;
249        assert!(test_driver_stop_called.load(Ordering::SeqCst));
250    }
251}