firmware_uefi/service/diagnostics/
mod.rs1use crate::UefiDevice;
18use gpa::Gpa;
19use guestmem::GuestMemory;
20use inspect::Inspect;
21use log::Log;
22use mesh::payload::Protobuf;
23use processor::ProcessingError;
24use uefi_specs::hyperv::debug_level::DEBUG_ERROR;
25use uefi_specs::hyperv::debug_level::DEBUG_INFO;
26use uefi_specs::hyperv::debug_level::DEBUG_WARN;
27
28mod accumulator;
29mod gpa;
30mod header;
31mod log;
32mod processor;
33
34pub const DEFAULT_LOGS_PER_PERIOD: u32 = 150;
36
37pub const WATCHDOG_LOGS_PER_PERIOD: u32 = 2000;
39
40fn emit_log_ratelimited(log: &Log, limit: u32) {
46 if log.debug_level & DEBUG_ERROR != 0 {
47 tracelimit::error_ratelimited!(
48 limit: limit,
49 debug_level = %log.debug_level_str(),
50 ticks = log.ticks(),
51 phase = %log.phase_str(),
52 log_message = log.message_trimmed(),
53 "EFI log entry"
54 )
55 } else if log.debug_level & DEBUG_WARN != 0 {
56 tracelimit::warn_ratelimited!(
57 limit: limit,
58 debug_level = %log.debug_level_str(),
59 ticks = log.ticks(),
60 phase = %log.phase_str(),
61 log_message = log.message_trimmed(),
62 "EFI log entry"
63 )
64 } else {
65 tracelimit::info_ratelimited!(
66 limit: limit,
67 debug_level = %log.debug_level_str(),
68 ticks = log.ticks(),
69 phase = %log.phase_str(),
70 log_message = log.message_trimmed(),
71 "EFI log entry"
72 )
73 }
74}
75
76fn emit_log_unrestricted(log: &Log) {
81 if log.debug_level & DEBUG_ERROR != 0 {
82 tracing::error!(
83 debug_level = %log.debug_level_str(),
84 ticks = log.ticks(),
85 phase = %log.phase_str(),
86 log_message = log.message_trimmed(),
87 "EFI log entry"
88 )
89 } else if log.debug_level & DEBUG_WARN != 0 {
90 tracing::warn!(
91 debug_level = %log.debug_level_str(),
92 ticks = log.ticks(),
93 phase = %log.phase_str(),
94 log_message = log.message_trimmed(),
95 "EFI log entry"
96 )
97 } else {
98 tracing::info!(
99 debug_level = %log.debug_level_str(),
100 ticks = log.ticks(),
101 phase = %log.phase_str(),
102 log_message = log.message_trimmed(),
103 "EFI log entry"
104 )
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Protobuf)]
110#[mesh(transparent)]
111pub struct LogLevel(u32);
112
113impl LogLevel {
114 pub const fn make_default() -> Self {
116 Self(DEBUG_ERROR | DEBUG_WARN)
117 }
118
119 pub const fn make_info() -> Self {
121 Self(DEBUG_ERROR | DEBUG_WARN | DEBUG_INFO)
122 }
123
124 pub const fn make_full() -> Self {
126 Self(u32::MAX)
127 }
128
129 pub fn should_log(self, raw_debug_level: u32) -> bool {
131 if self.0 == u32::MAX {
132 true } else {
134 (raw_debug_level & self.0) != 0
135 }
136 }
137}
138
139impl Default for LogLevel {
140 fn default() -> Self {
141 Self::make_default()
142 }
143}
144
145impl Inspect for LogLevel {
146 fn inspect(&self, req: inspect::Request<'_>) {
147 let human_readable = log::debug_level_to_string(self.0);
148 req.respond()
149 .field("raw_value", self.0)
150 .field("debug_levels", human_readable.as_ref());
151 }
152}
153
154#[derive(Inspect)]
156pub struct DiagnosticsServices {
157 gpa: Option<Gpa>,
159 processed: bool,
161 log_level: LogLevel,
163}
164
165impl DiagnosticsServices {
166 pub fn new(log_level: LogLevel) -> DiagnosticsServices {
168 DiagnosticsServices {
169 gpa: None,
170 processed: false,
171 log_level,
172 }
173 }
174
175 pub fn reset(&mut self) {
177 self.gpa = None;
178 self.processed = false;
179 }
180
181 pub fn set_gpa(&mut self, gpa: u32) {
183 self.gpa = Gpa::new(gpa).ok();
184 }
185
186 pub fn process_diagnostics<F>(
194 &mut self,
195 allow_reprocess: bool,
196 gm: &GuestMemory,
197 log_level_override: Option<LogLevel>,
198 log_handler: F,
199 ) -> Result<(), ProcessingError>
200 where
201 F: FnMut(&Log),
202 {
203 if self.processed && !allow_reprocess {
205 tracelimit::warn_ratelimited!("Already processed diagnostics, skipping");
206 return Ok(());
207 }
208
209 self.processed = true;
211
212 let effective_log_level = log_level_override.unwrap_or(self.log_level);
214
215 processor::process_diagnostics_internal(self.gpa, gm, effective_log_level, log_handler)
217 }
218}
219
220pub(crate) enum DiagnosticsEmitter {
222 Tracing { limit: Option<u32> },
224 String,
226}
227
228impl UefiDevice {
229 pub(crate) fn process_diagnostics(
236 &mut self,
237 allow_reprocess: bool,
238 emitter: DiagnosticsEmitter,
239 log_level_override: Option<LogLevel>,
240 ) -> Result<Option<String>, ProcessingError> {
241 use std::fmt::Write;
242 let mut output = match emitter {
243 DiagnosticsEmitter::String => Some(String::new()),
244 DiagnosticsEmitter::Tracing { .. } => None,
245 };
246
247 if let Err(error) = self.service.diagnostics.process_diagnostics(
248 allow_reprocess,
249 &self.gm,
250 log_level_override,
251 |log| {
252 if let Some(out) = &mut output {
253 let _ = writeln!(
254 out,
255 "({} ticks) [{}] [{}]: {}",
256 log.ticks(),
257 log.debug_level_str(),
258 log.phase_str(),
259 log.message_trimmed(),
260 );
261 } else if let DiagnosticsEmitter::Tracing { limit } = emitter {
262 match limit {
263 Some(limit) => emit_log_ratelimited(log, limit),
264 None => emit_log_unrestricted(log),
265 }
266 }
267 },
268 ) {
269 match emitter {
270 DiagnosticsEmitter::Tracing { .. } => {
271 tracelimit::error_ratelimited!(
272 error = &error as &dyn std::error::Error,
273 "failed to process diagnostics buffer"
274 );
275 return Ok(None);
277 }
278 DiagnosticsEmitter::String => return Err(error),
279 }
280 }
281
282 Ok(output)
283 }
284}
285
286mod save_restore {
287 use super::*;
288 use vmcore::save_restore::RestoreError;
289 use vmcore::save_restore::SaveError;
290 use vmcore::save_restore::SaveRestore;
291
292 mod state {
293 use super::LogLevel;
294 use mesh::payload::Protobuf;
295 use vmcore::save_restore::SavedStateRoot;
296
297 #[derive(Protobuf, SavedStateRoot)]
298 #[mesh(package = "firmware.uefi.diagnostics")]
299 pub struct SavedState {
300 #[mesh(1)]
301 pub gpa: Option<u32>,
302 #[mesh(2)]
303 pub did_flush: bool,
304 #[mesh(3)]
305 pub log_level: LogLevel,
306 }
307 }
308
309 impl SaveRestore for DiagnosticsServices {
310 type SavedState = state::SavedState;
311
312 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
313 Ok(state::SavedState {
314 gpa: self.gpa.map(|g| g.get()),
315 did_flush: self.processed,
316 log_level: self.log_level,
317 })
318 }
319
320 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
321 let state::SavedState {
322 gpa,
323 did_flush,
324 log_level,
325 } = state;
326 self.gpa = gpa.and_then(|g| Gpa::new(g).ok());
327 self.processed = did_flush;
328 self.log_level = log_level;
329 Ok(())
330 }
331 }
332}