Skip to main content

driver_manager_firmware_crash/
firmware_crash_service.rs

1// Copyright 2026 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 fidl::Peered;
6use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
7use futures::StreamExt;
8use log::warn;
9use std::cell::RefCell;
10use std::collections::HashMap;
11use std::rc::{Rc, Weak};
12use zx::HandleBased;
13use {fidl_fuchsia_firmware_crash as ffc, fuchsia_async as fasync};
14
15pub struct FirmwareCrashService {
16    inner: Rc<RefCell<FirmwareCrashInner>>,
17    scope: fasync::Scope,
18}
19
20struct FirmwareCrashInner {
21    crash_count: HashMap<String, u32>,
22    crashes: Vec<ffc::Crash>,
23    watchers: Vec<Weak<RefCell<Watcher>>>,
24}
25
26pub struct Watcher {
27    parent: Weak<RefCell<FirmwareCrashInner>>,
28    crash_index: usize,
29    completer: Option<ffc::WatcherGetCrashResponder>,
30    event: Option<zx::EventPair>,
31}
32
33impl Default for FirmwareCrashService {
34    fn default() -> Self {
35        Self {
36            inner: Rc::new(RefCell::new(FirmwareCrashInner {
37                crash_count: HashMap::new(),
38                crashes: Vec::new(),
39                watchers: Vec::new(),
40            })),
41            scope: fasync::Scope::new_with_name("firmware_crash_service"),
42        }
43    }
44}
45
46impl FirmwareCrashService {
47    pub fn publish(self: &Rc<Self>, fs: &mut ServiceFs<ServiceObjLocal<'_, ()>>) {
48        let this = self.clone();
49        fs.dir("svc").add_fidl_service(move |stream: ffc::ReporterRequestStream| {
50            let this_clone1 = this.clone();
51            let this_clone2 = this.clone();
52            this_clone1.scope.spawn_local(async move {
53                if let Err(e) = this_clone2.serve_reporter(stream).await {
54                    warn!("Failed to serve fuchsia.firmware.crash.Reporter: {}", e);
55                }
56            });
57        });
58
59        let this = self.clone();
60        fs.dir("svc").add_fidl_service(move |stream: ffc::WatcherRequestStream| {
61            let this_clone1 = this.clone();
62            let this_clone2 = this.clone();
63            this_clone1.scope.spawn_local(async move {
64                if let Err(e) = this_clone2.serve_watcher(stream).await {
65                    warn!("Failed to serve fuchsia.firmware.crash.Watcher: {}", e);
66                }
67            });
68        });
69    }
70
71    async fn serve_reporter(
72        self: Rc<Self>,
73        mut stream: ffc::ReporterRequestStream,
74    ) -> Result<(), fidl::Error> {
75        while let Some(request) = stream.next().await {
76            match request? {
77                ffc::ReporterRequest::Report { mut payload, .. } => {
78                    self.report(&mut payload);
79                }
80                ffc::ReporterRequest::_UnknownMethod { ordinal, .. } => {
81                    warn!("fuchsia.firmware.crash/Reporter received unknown method: {}", ordinal);
82                }
83            }
84        }
85        Ok(())
86    }
87
88    fn report(&self, crash: &mut ffc::Crash) {
89        let watchers = {
90            let mut inner = self.inner.borrow_mut();
91
92            if let Some(subsystem) = &crash.subsystem_name {
93                let count = inner.crash_count.entry(subsystem.clone()).or_insert(0);
94                *count += 1;
95                crash.count = Some(*count);
96            }
97
98            inner.crashes.push(clone_crash(crash));
99
100            let mut active_watchers = Vec::new();
101            inner.watchers.retain(|w| {
102                if let Some(watcher) = w.upgrade() {
103                    active_watchers.push(watcher);
104                    true
105                } else {
106                    false
107                }
108            });
109            active_watchers
110        };
111
112        for watcher in watchers {
113            watcher.borrow_mut().new_crash_available();
114        }
115    }
116
117    async fn serve_watcher(
118        self: Rc<Self>,
119        stream: ffc::WatcherRequestStream,
120    ) -> Result<(), fidl::Error> {
121        let watcher = Rc::new(RefCell::new(Watcher {
122            parent: Rc::downgrade(&self.inner),
123            crash_index: 0,
124            completer: None,
125            event: None,
126        }));
127
128        self.inner.borrow_mut().watchers.push(Rc::downgrade(&watcher));
129
130        let mut stream = stream;
131        while let Some(request) = stream.next().await {
132            match request? {
133                ffc::WatcherRequest::GetCrash { payload, responder } => {
134                    watcher.borrow_mut().get_crash(payload, responder);
135                }
136                ffc::WatcherRequest::GetCrashEvent { responder } => {
137                    watcher.borrow_mut().get_crash_event(responder);
138                }
139                ffc::WatcherRequest::_UnknownMethod { ordinal, .. } => {
140                    warn!("fuchsia.firmware.crash/Watcher received unknown method: {}", ordinal);
141                }
142            }
143        }
144        Ok(())
145    }
146}
147
148impl Watcher {
149    fn new_crash_available(&mut self) {
150        let Some(parent) = self.parent.upgrade() else {
151            return;
152        };
153        let inner = parent.borrow();
154
155        if let Some(responder) = self.completer.take() {
156            let crash = clone_crash(&inner.crashes[self.crash_index]);
157            let _ = responder.send(Ok(crash));
158            self.crash_index += 1;
159        } else if let Some(event) = &self.event {
160            let _ = event.signal_peer(zx::Signals::NONE, zx::Signals::USER_0);
161        }
162    }
163
164    fn get_crash(
165        &mut self,
166        request: ffc::WatcherGetCrashRequest,
167        responder: ffc::WatcherGetCrashResponder,
168    ) {
169        if self.completer.is_some() {
170            let _ = responder.send(Err(ffc::Error::AlreadyPending));
171            return;
172        }
173
174        let Some(parent) = self.parent.upgrade() else {
175            return;
176        };
177
178        let inner = parent.borrow();
179        if inner.crashes.len() > self.crash_index {
180            let crash = clone_crash(&inner.crashes[self.crash_index]);
181            self.crash_index += 1;
182            if let Some(event) = &self.event
183                && self.crash_index == inner.crashes.len()
184            {
185                let _ = event.signal_peer(zx::Signals::USER_0, zx::Signals::NONE);
186            }
187            let _ = responder.send(Ok(crash));
188            return;
189        }
190
191        if request.wait_for_crash.unwrap_or(true) {
192            self.completer = Some(responder);
193        } else {
194            let _ = responder.send(Err(ffc::Error::NoCrashAvailable));
195        }
196    }
197
198    fn get_crash_event(&mut self, responder: ffc::WatcherGetCrashEventResponder) {
199        let (h1, h2) = zx::EventPair::create();
200        self.event = Some(h1);
201        let _ = responder.send(h2);
202    }
203}
204
205fn clone_crash(crash: &ffc::Crash) -> ffc::Crash {
206    ffc::Crash {
207        subsystem_name: crash.subsystem_name.clone(),
208        timestamp: crash.timestamp,
209        reason: crash.reason.clone(),
210        count: crash.count,
211        firmware_version: crash.firmware_version.clone(),
212        crash_dump: crash
213            .crash_dump
214            .as_ref()
215            .and_then(|vmo| vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).ok()),
216        ..Default::default()
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use fidl::endpoints::create_proxy_and_stream;
224    use futures::FutureExt;
225
226    #[fasync::run_singlethreaded(test)]
227    async fn test_report_and_watch() {
228        let service = Rc::new(FirmwareCrashService::default());
229        let (reporter, reporter_stream) = create_proxy_and_stream::<ffc::ReporterMarker>();
230        let (watcher, watcher_stream) = create_proxy_and_stream::<ffc::WatcherMarker>();
231
232        let service_clone1 = service.clone();
233        let service_clone2 = service.clone();
234        service_clone1.scope.spawn_local(async move {
235            service_clone2.serve_reporter(reporter_stream).await.unwrap();
236        });
237
238        let service_clone1 = service.clone();
239        let service_clone2 = service.clone();
240        service_clone1.scope.spawn_local(async move {
241            service_clone2.serve_watcher(watcher_stream).await.unwrap();
242        });
243
244        // 1. Report a crash
245        let crash =
246            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
247        reporter.report(crash).unwrap();
248
249        // 2. Watch for crash
250        let result = watcher
251            .get_crash(&ffc::WatcherGetCrashRequest {
252                wait_for_crash: Some(false),
253                ..Default::default()
254            })
255            .await
256            .unwrap();
257        let received = result.unwrap();
258        assert_eq!(received.subsystem_name.unwrap(), "test-subsystem");
259        assert_eq!(received.count.unwrap(), 1);
260
261        // 3. Report another crash for same subsystem
262        let crash2 =
263            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
264        reporter.report(crash2).unwrap();
265
266        // 4. Watch again
267        let result = watcher
268            .get_crash(&ffc::WatcherGetCrashRequest {
269                wait_for_crash: Some(false),
270                ..Default::default()
271            })
272            .await
273            .unwrap();
274        let received2 = result.unwrap();
275        assert_eq!(received2.subsystem_name.unwrap(), "test-subsystem");
276        assert_eq!(received2.count.unwrap(), 2);
277    }
278
279    #[fasync::run_singlethreaded(test)]
280    async fn test_wait_for_crash() {
281        let service = Rc::new(FirmwareCrashService::default());
282        let (reporter, reporter_stream) = create_proxy_and_stream::<ffc::ReporterMarker>();
283        let (watcher, watcher_stream) = create_proxy_and_stream::<ffc::WatcherMarker>();
284
285        let service_clone1 = service.clone();
286        let service_clone2 = service.clone();
287        service_clone1.scope.spawn_local(async move {
288            service_clone2.serve_reporter(reporter_stream).await.unwrap();
289        });
290
291        let service_clone1 = service.clone();
292        let service_clone2 = service.clone();
293        service_clone1.scope.spawn_local(async move {
294            service_clone2.serve_watcher(watcher_stream).await.unwrap();
295        });
296
297        // 1. Get crash (should hang)
298        let get_fut = watcher.get_crash(&ffc::WatcherGetCrashRequest {
299            wait_for_crash: Some(true),
300            ..Default::default()
301        });
302
303        // 2. Report a crash
304        let crash =
305            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
306        reporter.report(crash).unwrap();
307
308        // 3. Future should complete
309        let result = get_fut.await.unwrap();
310        let received = result.unwrap();
311        assert_eq!(received.subsystem_name.unwrap(), "test-subsystem");
312    }
313
314    #[fasync::run_singlethreaded(test)]
315    async fn test_get_crash_event() {
316        let service = Rc::new(FirmwareCrashService::default());
317        let (reporter, reporter_stream) = create_proxy_and_stream::<ffc::ReporterMarker>();
318        let (watcher, watcher_stream) = create_proxy_and_stream::<ffc::WatcherMarker>();
319
320        let service_clone1 = service.clone();
321        let service_clone2 = service.clone();
322        service_clone1.scope.spawn_local(async move {
323            service_clone2.serve_reporter(reporter_stream).await.unwrap();
324        });
325
326        let service_clone1 = service.clone();
327        let service_clone2 = service.clone();
328        service_clone1.scope.spawn_local(async move {
329            service_clone2.serve_watcher(watcher_stream).await.unwrap();
330        });
331
332        let event = watcher.get_crash_event().await.unwrap();
333
334        // 1. Report a crash
335        let crash =
336            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
337        reporter.report(crash).unwrap();
338
339        // 2. Event should be signaled
340        fasync::OnSignals::new(&event, zx::Signals::USER_0).await.unwrap();
341
342        // 3. Get the crash
343        let _ = watcher
344            .get_crash(&ffc::WatcherGetCrashRequest {
345                wait_for_crash: Some(false),
346                ..Default::default()
347            })
348            .await
349            .unwrap()
350            .unwrap();
351
352        // 4. Signal should be cleared if no more crashes
353        let timer =
354            fasync::Timer::new(zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(10)))
355                .fuse();
356        let signals = fasync::OnSignals::new(&event, zx::Signals::USER_0).fuse();
357        futures::pin_mut!(timer, signals);
358        futures::select! {
359            _ = signals => panic!("Signal should have been cleared"),
360            _ = timer => (),
361        }
362    }
363}