1use core::future::Future;
6use fdf_component::{Driver, DriverContext, DriverError};
7use fidl_fuchsia_hardware_power as fhw_power;
8use fidl_fuchsia_io as fio;
9use fidl_fuchsia_power_broker as fpower_broker;
10use fidl_fuchsia_power_system as fpower;
11use fuchsia_async as fasync;
12use fuchsia_component::client::{Connect, SVC_DIR};
13use fuchsia_component::directory::Directory;
14use futures::TryStreamExt;
15use log::{error, warn};
16use std::sync::{Arc, Weak};
17use zx::Status;
18
19use fidl_next as _;
20use fidl_next_fuchsia_hardware_powerdomain as fpowerdomain;
21
22pub trait SuspendableDriver: Driver {
25 fn suspend(&self) -> impl Future<Output = ()> + Send;
28
29 fn resume(&self) -> impl Future<Output = ()> + Send;
34
35 fn suspend_enabled(&self) -> bool;
38}
39
40pub struct Suspendable<T: Driver> {
42 #[expect(unused)]
43 scope: Option<fasync::Scope>,
44 driver: Arc<T>,
45}
46
47async fn run_suspend_blocker<T: SuspendableDriver>(
48 driver: Weak<T>,
49 mut service: fpower::SuspendBlockerRequestStream,
50) {
51 use fpower::SuspendBlockerRequest::*;
52 while let Some(req) = service.try_next().await.unwrap() {
53 match req {
54 BeforeSuspend { responder, .. } => {
55 if let Some(driver) = driver.upgrade() {
56 driver.suspend().await;
57 } else {
58 return;
59 }
60 let _ = responder.send();
61 }
62 AfterResume { responder, .. } => {
63 if let Some(driver) = driver.upgrade() {
64 driver.resume().await;
65 } else {
66 return;
67 }
68 let _ = responder.send();
69 }
70 _ => {
72 warn!("Received unknown sag listener request");
73 }
74 }
75 }
76}
77
78async fn run_element_runner<T: SuspendableDriver>(
79 driver: Weak<T>,
80 mut service: fpower_broker::ElementRunnerRequestStream,
81) {
82 let mut first_activation_occurred = false;
83 while let Some(req) = service.try_next().await.unwrap_or_default() {
84 if let fpower_broker::ElementRunnerRequest::SetLevel { level, responder } = req {
85 let Some(driver) = driver.upgrade() else { return };
86 if level != fhw_power::FrameworkElementLevels::Off.into_primitive() as u8 {
87 if first_activation_occurred {
91 driver.resume().await;
92 }
93 } else {
94 driver.suspend().await;
95 }
96 let _ = responder.send();
97 first_activation_occurred = true;
98 }
99 }
100}
101
102impl<T: SuspendableDriver + Send + Sync> Driver for Suspendable<T> {
103 const NAME: &str = T::NAME;
104
105 async fn start(mut context: DriverContext) -> Result<Self, DriverError> {
106 let mut runner = context
107 .start_args
108 .power_element_args
109 .as_mut()
110 .and_then(|args| args.runner_server.take());
111
112 let (svc, svc_server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
113 context
114 .incoming
115 .open(
116 SVC_DIR,
117 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
118 svc_server.into_channel(),
119 )
120 .map_err(|error| {
121 error!(error:?; "Error opening svc directory");
122 Status::INTERNAL
123 })?;
124
125 let driver = Arc::new(T::start(context).await?);
126
127 let scope = if driver.suspend_enabled() {
128 let scope = fasync::Scope::new_with_name("suspend");
129 if let Some(runner) = runner.take() {
130 let weak_driver = Arc::downgrade(&driver);
131 scope.spawn(
132 async move { run_element_runner(weak_driver, runner.into_stream()).await },
133 );
134 } else {
135 let sag =
136 fpower::ActivityGovernorProxy::connect_at_dir_root(&svc).map_err(|error| {
137 error!(error:?; "Error connecting to sag");
138 Status::INTERNAL
139 })?;
140
141 let (client, server) = fidl::endpoints::create_endpoints();
142
143 let _ = sag
144 .register_suspend_blocker(
145 fpower::ActivityGovernorRegisterSuspendBlockerRequest {
146 suspend_blocker: Some(client),
147 name: Some(Self::NAME.into()),
148 ..Default::default()
149 },
150 )
151 .await
152 .map_err(|error| {
153 error!(error:?; "Error connecting to sag");
154 Status::INTERNAL
155 })?
156 .map_err(|error| {
157 error!(error:?; "Error connecting to sag");
158 Status::INTERNAL
159 })?;
160
161 let weak_driver = Arc::downgrade(&driver);
162 scope.spawn(
163 async move { run_suspend_blocker(weak_driver, server.into_stream()).await },
164 );
165 }
166 Some(scope)
167 } else {
168 None
169 };
170
171 Ok(Self { driver, scope })
172 }
173
174 async fn stop(&self) {
175 self.driver.stop().await;
176 }
177}
178
179pub trait PowerExt {
181 fn connect_to_powerdomain(
183 &self,
184 instance: &str,
185 ) -> Result<fidl_next::Client<fpowerdomain::Domain>, DriverError>;
186}
187
188impl PowerExt for DriverContext {
189 fn connect_to_powerdomain(
190 &self,
191 instance: &str,
192 ) -> Result<fidl_next::Client<fpowerdomain::Domain>, DriverError> {
193 let service = self
194 .incoming
195 .service::<fdf_component::ServiceInstance<fpowerdomain::Service>>()
196 .instance(instance)
197 .connect_next()?;
198 let (client, server) = fidl_next::fuchsia::create_channel();
199 service.domain(server)?;
200 Ok(client.spawn())
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use fdf_component::testing::harness::TestHarness;
208 use fidl_fuchsia_driver_framework as fdf;
209 use std::sync::atomic::{AtomicBool, Ordering};
210
211 struct TestDriver {
212 suspend_called: Arc<AtomicBool>,
213 resume_called: Arc<AtomicBool>,
214 suspend_enabled: bool,
215 stop_called: Arc<AtomicBool>,
216 }
217
218 impl Driver for TestDriver {
219 const NAME: &str = "test_driver";
220
221 async fn start(_context: DriverContext) -> Result<Self, DriverError> {
222 Ok(Self {
223 suspend_called: Arc::new(AtomicBool::new(false)),
224 resume_called: Arc::new(AtomicBool::new(false)),
225 suspend_enabled: true,
226 stop_called: Arc::new(AtomicBool::new(false)),
227 })
228 }
229
230 async fn stop(&self) {
231 self.stop_called.store(true, Ordering::SeqCst);
232 }
233 }
234
235 impl SuspendableDriver for TestDriver {
236 async fn suspend(&self) {
237 self.suspend_called.store(true, Ordering::SeqCst);
238 }
239
240 async fn resume(&self) {
241 self.resume_called.store(true, Ordering::SeqCst);
242 }
243
244 fn suspend_enabled(&self) -> bool {
245 self.suspend_enabled
246 }
247 }
248
249 #[fuchsia::test]
250 async fn test_suspend_resume_with_runner() {
251 let (runner_client, runner_server) =
252 fidl::endpoints::create_endpoints::<fpower_broker::ElementRunnerMarker>();
253
254 let mut harness = TestHarness::<Suspendable<TestDriver>>::new().set_power_element_args(
255 fdf::PowerElementArgs { runner_server: Some(runner_server), ..Default::default() },
256 );
257
258 let driver_under_test = harness.start_driver().await.expect("Failed to start driver");
259 let (test_driver_stop_called, test_driver_resume_called, test_driver_suspend_called) = {
260 let suspendable = driver_under_test.get_driver().expect("Failed to get driver");
261 (
262 suspendable.driver.stop_called.clone(),
263 suspendable.driver.resume_called.clone(),
264 suspendable.driver.suspend_called.clone(),
265 )
266 };
267
268 let runner_proxy = runner_client.into_proxy();
269
270 runner_proxy.set_level(1).await.expect("Failed to set level");
272 assert!(!test_driver_resume_called.load(Ordering::SeqCst));
273
274 runner_proxy.set_level(0).await.expect("Failed to set level");
276 assert!(test_driver_suspend_called.load(Ordering::SeqCst));
277 test_driver_suspend_called.store(false, Ordering::SeqCst);
278
279 runner_proxy.set_level(1).await.expect("Failed to set level");
281 assert!(test_driver_resume_called.load(Ordering::SeqCst));
282
283 driver_under_test.stop_driver().await;
284 assert!(test_driver_stop_called.load(Ordering::SeqCst));
285 }
286}