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 fuchsia_component::server::{ServiceFs, ServiceObjLocal};
6use futures::StreamExt;
7use log::warn;
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::rc::{Rc, Weak};
11use zx::HandleBased;
12use {fidl_fuchsia_firmware_crash as ffc, fuchsia_async as fasync};
13
14pub struct FirmwareCrashService {
15    inner: Rc<RefCell<FirmwareCrashInner>>,
16    scope: fasync::Scope,
17}
18
19struct FirmwareCrashInner {
20    crash_count: HashMap<String, u32>,
21    crashes: Vec<ffc::Crash>,
22    watchers: Vec<Weak<RefCell<Watcher>>>,
23}
24
25pub struct Watcher {
26    parent: Weak<RefCell<FirmwareCrashInner>>,
27    crash_index: usize,
28    completer: Option<ffc::WatcherGetCrashResponder>,
29}
30
31impl Default for FirmwareCrashService {
32    fn default() -> Self {
33        Self {
34            inner: Rc::new(RefCell::new(FirmwareCrashInner {
35                crash_count: HashMap::new(),
36                crashes: Vec::new(),
37                watchers: Vec::new(),
38            })),
39            scope: fasync::Scope::new_with_name("firmware_crash_service"),
40        }
41    }
42}
43
44impl FirmwareCrashService {
45    pub fn publish(self: &Rc<Self>, fs: &mut ServiceFs<ServiceObjLocal<'_, ()>>) {
46        let this = self.clone();
47        fs.dir("svc").add_fidl_service(move |stream: ffc::ReporterRequestStream| {
48            let this_clone1 = this.clone();
49            let this_clone2 = this.clone();
50            this_clone1.scope.spawn_local(async move {
51                if let Err(e) = this_clone2.serve_reporter(stream).await {
52                    warn!("Failed to serve fuchsia.firmware.crash.Reporter: {}", e);
53                }
54            });
55        });
56
57        let this = self.clone();
58        fs.dir("svc").add_fidl_service(move |stream: ffc::WatcherRequestStream| {
59            let this_clone1 = this.clone();
60            let this_clone2 = this.clone();
61            this_clone1.scope.spawn_local(async move {
62                if let Err(e) = this_clone2.serve_watcher(stream).await {
63                    warn!("Failed to serve fuchsia.firmware.crash.Watcher: {}", e);
64                }
65            });
66        });
67    }
68
69    async fn serve_reporter(
70        self: Rc<Self>,
71        mut stream: ffc::ReporterRequestStream,
72    ) -> Result<(), fidl::Error> {
73        while let Some(request) = stream.next().await {
74            match request? {
75                ffc::ReporterRequest::Report { mut payload, .. } => {
76                    self.report(&mut payload);
77                }
78                ffc::ReporterRequest::_UnknownMethod { ordinal, .. } => {
79                    warn!("fuchsia.firmware.crash/Reporter received unknown method: {}", ordinal);
80                }
81            }
82        }
83        Ok(())
84    }
85
86    fn report(&self, crash: &mut ffc::Crash) {
87        let watchers = {
88            let mut inner = self.inner.borrow_mut();
89
90            if let Some(subsystem) = &crash.subsystem_name {
91                let count = inner.crash_count.entry(subsystem.clone()).or_insert(0);
92                *count += 1;
93                crash.count = Some(*count);
94            }
95
96            inner.crashes.push(clone_crash(crash));
97
98            let mut active_watchers = Vec::new();
99            inner.watchers.retain(|w| {
100                if let Some(watcher) = w.upgrade() {
101                    active_watchers.push(watcher);
102                    true
103                } else {
104                    false
105                }
106            });
107            active_watchers
108        };
109
110        for watcher in watchers {
111            watcher.borrow_mut().new_crash_available();
112        }
113    }
114
115    async fn serve_watcher(
116        self: Rc<Self>,
117        stream: ffc::WatcherRequestStream,
118    ) -> Result<(), fidl::Error> {
119        let watcher = Rc::new(RefCell::new(Watcher {
120            parent: Rc::downgrade(&self.inner),
121            crash_index: 0,
122            completer: None,
123        }));
124
125        self.inner.borrow_mut().watchers.push(Rc::downgrade(&watcher));
126
127        let mut stream = stream;
128        while let Some(request) = stream.next().await {
129            match request? {
130                ffc::WatcherRequest::GetCrash { responder } => {
131                    watcher.borrow_mut().get_crash(responder);
132                }
133                ffc::WatcherRequest::_UnknownMethod { ordinal, .. } => {
134                    warn!("fuchsia.firmware.crash/Watcher received unknown method: {}", ordinal);
135                }
136            }
137        }
138        Ok(())
139    }
140}
141
142impl Watcher {
143    fn new_crash_available(&mut self) {
144        let Some(parent) = self.parent.upgrade() else {
145            return;
146        };
147        let inner = parent.borrow();
148
149        if let Some(responder) = self.completer.take() {
150            let crash = clone_crash(&inner.crashes[self.crash_index]);
151            let _ = responder.send(Ok(crash));
152            self.crash_index += 1;
153        }
154    }
155
156    fn get_crash(&mut self, responder: ffc::WatcherGetCrashResponder) {
157        if self.completer.is_some() {
158            let _ = responder.send(Err(ffc::Error::AlreadyPending));
159            return;
160        }
161
162        let Some(parent) = self.parent.upgrade() else {
163            return;
164        };
165
166        let inner = parent.borrow();
167        if inner.crashes.len() > self.crash_index {
168            let crash = clone_crash(&inner.crashes[self.crash_index]);
169            self.crash_index += 1;
170            let _ = responder.send(Ok(crash));
171            return;
172        }
173
174        self.completer = Some(responder);
175    }
176}
177
178fn clone_crash(crash: &ffc::Crash) -> ffc::Crash {
179    ffc::Crash {
180        subsystem_name: crash.subsystem_name.clone(),
181        timestamp: crash.timestamp,
182        reason: crash.reason.clone(),
183        count: crash.count,
184        firmware_version: crash.firmware_version.clone(),
185        crash_dump: crash
186            .crash_dump
187            .as_ref()
188            .and_then(|vmo| vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).ok()),
189        ..Default::default()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use fidl::endpoints::create_proxy_and_stream;
197
198    #[fasync::run_singlethreaded(test)]
199    async fn test_report_and_watch() {
200        let service = Rc::new(FirmwareCrashService::default());
201        let (reporter, reporter_stream) = create_proxy_and_stream::<ffc::ReporterMarker>();
202        let (watcher, watcher_stream) = create_proxy_and_stream::<ffc::WatcherMarker>();
203
204        let service_clone1 = service.clone();
205        let service_clone2 = service.clone();
206        service_clone1.scope.spawn_local(async move {
207            service_clone2.serve_reporter(reporter_stream).await.unwrap();
208        });
209
210        let service_clone1 = service.clone();
211        let service_clone2 = service.clone();
212        service_clone1.scope.spawn_local(async move {
213            service_clone2.serve_watcher(watcher_stream).await.unwrap();
214        });
215
216        // 1. Report a crash
217        let crash =
218            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
219        reporter.report(crash).unwrap();
220
221        // 2. Watch for crash
222        let result = watcher.get_crash().await.unwrap();
223        let received = result.unwrap();
224        assert_eq!(received.subsystem_name.unwrap(), "test-subsystem");
225        assert_eq!(received.count.unwrap(), 1);
226
227        // 3. Report another crash for same subsystem
228        let crash2 =
229            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
230        reporter.report(crash2).unwrap();
231
232        // 4. Watch again
233        let result = watcher.get_crash().await.unwrap();
234        let received2 = result.unwrap();
235        assert_eq!(received2.subsystem_name.unwrap(), "test-subsystem");
236        assert_eq!(received2.count.unwrap(), 2);
237    }
238
239    #[fasync::run_singlethreaded(test)]
240    async fn test_wait_for_crash() {
241        let service = Rc::new(FirmwareCrashService::default());
242        let (reporter, reporter_stream) = create_proxy_and_stream::<ffc::ReporterMarker>();
243        let (watcher, watcher_stream) = create_proxy_and_stream::<ffc::WatcherMarker>();
244
245        let service_clone1 = service.clone();
246        let service_clone2 = service.clone();
247        service_clone1.scope.spawn_local(async move {
248            service_clone2.serve_reporter(reporter_stream).await.unwrap();
249        });
250
251        let service_clone1 = service.clone();
252        let service_clone2 = service.clone();
253        service_clone1.scope.spawn_local(async move {
254            service_clone2.serve_watcher(watcher_stream).await.unwrap();
255        });
256
257        // 1. Get crash (should hang)
258        let get_fut = watcher.get_crash();
259
260        // 2. Report a crash
261        let crash =
262            ffc::Crash { subsystem_name: Some("test-subsystem".to_string()), ..Default::default() };
263        reporter.report(crash).unwrap();
264
265        // 3. Future should complete
266        let result = get_fut.await.unwrap();
267        let received = result.unwrap();
268        assert_eq!(received.subsystem_name.unwrap(), "test-subsystem");
269    }
270}