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
47impl<T: Driver> core::ops::Deref for Suspendable<T> {
48 type Target = T;
49
50 fn deref(&self) -> &Self::Target {
51 &self.driver
52 }
53}
54
55async fn run_suspend_blocker<T: SuspendableDriver>(
56 driver: Weak<T>,
57 mut service: fpower::SuspendBlockerRequestStream,
58) {
59 use fpower::SuspendBlockerRequest::*;
60 while let Some(req) = service.try_next().await.unwrap() {
61 match req {
62 BeforeSuspend { responder, .. } => {
63 if let Some(driver) = driver.upgrade() {
64 driver.suspend().await;
65 } else {
66 return;
67 }
68 let _ = responder.send();
69 }
70 AfterResume { responder, .. } => {
71 if let Some(driver) = driver.upgrade() {
72 driver.resume().await;
73 } else {
74 return;
75 }
76 let _ = responder.send();
77 }
78 _ => {
80 warn!("Received unknown sag listener request");
81 }
82 }
83 }
84}
85
86async fn run_element_runner<T: SuspendableDriver>(
87 driver: Weak<T>,
88 mut service: fpower_broker::ElementRunnerRequestStream,
89) {
90 let mut first_activation_occurred = false;
91 while let Some(req) = service.try_next().await.unwrap_or_default() {
92 if let fpower_broker::ElementRunnerRequest::SetLevel { level, responder } = req {
93 let Some(driver) = driver.upgrade() else { return };
94 if level != fhw_power::FrameworkElementLevels::Off.into_primitive() as u8 {
95 if first_activation_occurred {
99 driver.resume().await;
100 }
101 } else {
102 driver.suspend().await;
103 }
104 let _ = responder.send();
105 first_activation_occurred = true;
106 }
107 }
108}
109
110impl<T: SuspendableDriver + Send + Sync> Driver for Suspendable<T> {
111 const NAME: &str = T::NAME;
112
113 async fn start(mut context: DriverContext) -> Result<Self, DriverError> {
114 let mut runner = context
115 .start_args
116 .power_element_args
117 .as_mut()
118 .and_then(|args| args.runner_server.take());
119
120 let (svc, svc_server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
121 context
122 .incoming
123 .open(
124 SVC_DIR,
125 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
126 svc_server.into_channel(),
127 )
128 .map_err(|error| {
129 error!(error:?; "Error opening svc directory");
130 Status::INTERNAL
131 })?;
132
133 let driver = Arc::new(T::start(context).await?);
134
135 let scope = if driver.suspend_enabled() {
136 let scope = fasync::Scope::new_with_name("suspend");
137 if let Some(runner) = runner.take() {
138 let runner = fidl::endpoints::ServerEnd::<fpower_broker::ElementRunnerMarker>::new(
139 runner.into_untyped(),
140 );
141 let weak_driver = Arc::downgrade(&driver);
142 scope.spawn(
143 async move { run_element_runner(weak_driver, runner.into_stream()).await },
144 );
145 } else {
146 let sag =
147 fpower::ActivityGovernorProxy::connect_at_dir_root(&svc).map_err(|error| {
148 error!(error:?; "Error connecting to sag");
149 Status::INTERNAL
150 })?;
151
152 let (client, server) = fidl::endpoints::create_endpoints();
153
154 let _ = sag
155 .register_suspend_blocker(
156 fpower::ActivityGovernorRegisterSuspendBlockerRequest {
157 suspend_blocker: Some(client),
158 name: Some(Self::NAME.into()),
159 ..Default::default()
160 },
161 )
162 .await
163 .map_err(|error| {
164 error!(error:?; "Error connecting to sag");
165 Status::INTERNAL
166 })?
167 .map_err(|error| {
168 error!(error:?; "Error connecting to sag");
169 Status::INTERNAL
170 })?;
171
172 let weak_driver = Arc::downgrade(&driver);
173 scope.spawn(
174 async move { run_suspend_blocker(weak_driver, server.into_stream()).await },
175 );
176 }
177 Some(scope)
178 } else {
179 None
180 };
181
182 Ok(Self { driver, scope })
183 }
184
185 async fn stop(&self) {
186 self.driver.stop().await;
187 }
188
189 async fn health_check(
190 &self,
191 request: fdf_component::HealthCheckRequest,
192 ) -> Result<fdf_component::HealthCheckResponse, fdf_component::HealthCheckError> {
193 self.driver.health_check(request).await
194 }
195}
196
197pub trait PowerExt {
199 fn connect_to_powerdomain(
201 &self,
202 instance: &str,
203 ) -> Result<fidl_next::Client<fpowerdomain::Domain>, DriverError>;
204}
205
206impl PowerExt for DriverContext {
207 fn connect_to_powerdomain(
208 &self,
209 instance: &str,
210 ) -> Result<fidl_next::Client<fpowerdomain::Domain>, DriverError> {
211 let service = self
212 .incoming
213 .service::<fdf_component::ServiceInstance<fpowerdomain::Service>>()
214 .instance(instance)
215 .connect_next()?;
216 let (client, server) = fidl_next::fuchsia::create_channel();
217 service.domain(server)?;
218 Ok(client.spawn())
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use fdf_component::testing::harness::TestHarness;
226 use fdf_component::{
227 HealthCheckError, HealthCheckRequest, HealthCheckResponse, HealthCondition, HealthStatus,
228 };
229 use fidl_next_fuchsia_driver_framework as fdf;
230 use std::sync::atomic::{AtomicBool, Ordering};
231
232 struct TestDriver {
233 suspend_called: Arc<AtomicBool>,
234 resume_called: Arc<AtomicBool>,
235 suspend_enabled: bool,
236 stop_called: Arc<AtomicBool>,
237 health_check_called: AtomicBool,
238 }
239
240 impl Driver for TestDriver {
241 const NAME: &str = "test_driver";
242
243 async fn start(_context: DriverContext) -> Result<Self, DriverError> {
244 Ok(Self {
245 suspend_called: Arc::new(AtomicBool::new(false)),
246 resume_called: Arc::new(AtomicBool::new(false)),
247 suspend_enabled: true,
248 stop_called: Arc::new(AtomicBool::new(false)),
249 health_check_called: AtomicBool::new(false),
250 })
251 }
252
253 async fn stop(&self) {
254 self.stop_called.store(true, Ordering::SeqCst);
255 }
256
257 async fn health_check(
258 &self,
259 request: HealthCheckRequest,
260 ) -> Result<HealthCheckResponse, HealthCheckError> {
261 self.health_check_called.store(true, Ordering::SeqCst);
262 match request {
263 HealthCheckRequest::Fast => Ok(HealthCheckResponse {
264 conditions: Some(vec![HealthCondition {
265 status: Some(HealthStatus::Degraded),
266 reason_code: Some("TEST_DEGRADED".to_string()),
267 ..Default::default()
268 }]),
269 ..Default::default()
270 }),
271 _ => Err(HealthCheckError::NotSupported),
272 }
273 }
274 }
275
276 impl SuspendableDriver for TestDriver {
277 async fn suspend(&self) {
278 self.suspend_called.store(true, Ordering::SeqCst);
279 }
280
281 async fn resume(&self) {
282 self.resume_called.store(true, Ordering::SeqCst);
283 }
284
285 fn suspend_enabled(&self) -> bool {
286 self.suspend_enabled
287 }
288 }
289
290 #[fuchsia::test]
291 async fn test_suspend_resume_with_runner() {
292 let (runner_client, runner_server) =
293 fidl::endpoints::create_endpoints::<fpower_broker::ElementRunnerMarker>();
294
295 let mut harness = TestHarness::<Suspendable<TestDriver>>::new().set_power_element_args(
296 fdf::PowerElementArgs {
297 runner_server: Some(fidl_next::ServerEnd::from_untyped(
298 runner_server.into_channel(),
299 )),
300 ..Default::default()
301 },
302 );
303
304 let driver_under_test = harness.start_driver().await.expect("Failed to start driver");
305 let (test_driver_stop_called, test_driver_resume_called, test_driver_suspend_called) = {
306 let suspendable = driver_under_test.get_driver().expect("Failed to get driver");
307 (
308 suspendable.driver.stop_called.clone(),
309 suspendable.driver.resume_called.clone(),
310 suspendable.driver.suspend_called.clone(),
311 )
312 };
313
314 let runner_proxy = runner_client.into_proxy();
315
316 runner_proxy.set_level(1).await.expect("Failed to set level");
318 assert!(!test_driver_resume_called.load(Ordering::SeqCst));
319
320 runner_proxy.set_level(0).await.expect("Failed to set level");
322 assert!(test_driver_suspend_called.load(Ordering::SeqCst));
323 test_driver_suspend_called.store(false, Ordering::SeqCst);
324
325 runner_proxy.set_level(1).await.expect("Failed to set level");
327 assert!(test_driver_resume_called.load(Ordering::SeqCst));
328
329 driver_under_test.stop_driver().await;
330 assert!(test_driver_stop_called.load(Ordering::SeqCst));
331 }
332
333 #[fuchsia::test]
334 async fn test_health_check_forwards_to_inner_driver() {
335 let (_runner_client, runner_server) =
336 fidl::endpoints::create_endpoints::<fpower_broker::ElementRunnerMarker>();
337 let mut harness = TestHarness::<Suspendable<TestDriver>>::new().set_power_element_args(
338 fdf::PowerElementArgs {
339 runner_server: Some(fidl_next::ServerEnd::from_untyped(
340 runner_server.into_channel(),
341 )),
342 ..Default::default()
343 },
344 );
345 let driver_under_test = harness.start_driver().await.expect("Failed to start driver");
346 let suspendable = driver_under_test.get_driver().expect("Failed to get driver");
347
348 assert!(!suspendable.health_check_called.load(Ordering::SeqCst));
350
351 let result = <Suspendable<TestDriver> as Driver>::health_check(
354 suspendable,
355 HealthCheckRequest::Fast,
356 )
357 .await;
358 assert!(suspendable.health_check_called.load(Ordering::SeqCst));
359 let response = result.expect("FAST is answered by the wrapped driver");
360 let conditions = response.conditions.expect("conditions");
361 assert_eq!(conditions.len(), 1);
362 assert_eq!(conditions[0].status, Some(HealthStatus::Degraded));
363 assert_eq!(conditions[0].reason_code.as_deref(), Some("TEST_DEGRADED"));
364
365 let result = <Suspendable<TestDriver> as Driver>::health_check(
366 suspendable,
367 HealthCheckRequest::Deep,
368 )
369 .await;
370 assert_eq!(result, Err(HealthCheckError::NotSupported));
371
372 driver_under_test.stop_driver().await;
373 }
374}