firmware_uefi/service/diagnostics/
mod.rs1use crate::UefiDevice;
18use firmware_uefi_resources::LogLevel;
19use gpa::Gpa;
20use guestmem::GuestMemory;
21use inspect::Inspect;
22use log::Log;
23use processor::ProcessingError;
24use uefi_specs::hyperv::debug_level::DEBUG_ERROR;
25use uefi_specs::hyperv::debug_level::DEBUG_WARN;
26
27mod accumulator;
28mod gpa;
29mod header;
30mod log;
31mod processor;
32
33pub const DEFAULT_LOGS_PER_PERIOD: u32 = 150;
35
36pub const WATCHDOG_LOGS_PER_PERIOD: u32 = 2000;
38
39fn emit_log_ratelimited(log: &Log, limit: u32) {
45 if log.debug_level & DEBUG_ERROR != 0 {
46 tracelimit::error_ratelimited!(
47 limit: limit,
48 debug_level = %log.debug_level_str(),
49 ticks = log.ticks(),
50 phase = %log.phase_str(),
51 log_message = log.message_trimmed(),
52 "EFI log entry"
53 )
54 } else if log.debug_level & DEBUG_WARN != 0 {
55 tracelimit::warn_ratelimited!(
56 limit: limit,
57 debug_level = %log.debug_level_str(),
58 ticks = log.ticks(),
59 phase = %log.phase_str(),
60 log_message = log.message_trimmed(),
61 "EFI log entry"
62 )
63 } else {
64 tracelimit::info_ratelimited!(
65 limit: limit,
66 debug_level = %log.debug_level_str(),
67 ticks = log.ticks(),
68 phase = %log.phase_str(),
69 log_message = log.message_trimmed(),
70 "EFI log entry"
71 )
72 }
73}
74
75fn emit_log_unrestricted(log: &Log) {
80 if log.debug_level & DEBUG_ERROR != 0 {
81 tracing::error!(
82 debug_level = %log.debug_level_str(),
83 ticks = log.ticks(),
84 phase = %log.phase_str(),
85 log_message = log.message_trimmed(),
86 "EFI log entry"
87 )
88 } else if log.debug_level & DEBUG_WARN != 0 {
89 tracing::warn!(
90 debug_level = %log.debug_level_str(),
91 ticks = log.ticks(),
92 phase = %log.phase_str(),
93 log_message = log.message_trimmed(),
94 "EFI log entry"
95 )
96 } else {
97 tracing::info!(
98 debug_level = %log.debug_level_str(),
99 ticks = log.ticks(),
100 phase = %log.phase_str(),
101 log_message = log.message_trimmed(),
102 "EFI log entry"
103 )
104 }
105}
106
107#[derive(Inspect)]
109pub struct DiagnosticsServices {
110 gpa: Option<Gpa>,
112 processed: bool,
114 log_level: LogLevel,
116}
117
118impl DiagnosticsServices {
119 pub fn new(log_level: LogLevel) -> DiagnosticsServices {
121 DiagnosticsServices {
122 gpa: None,
123 processed: false,
124 log_level,
125 }
126 }
127
128 pub fn reset(&mut self) {
130 self.gpa = None;
131 self.processed = false;
132 }
133
134 pub fn set_gpa(&mut self, gpa: u32) {
136 self.gpa = Gpa::new(gpa).ok();
137 }
138
139 pub fn process_diagnostics<F>(
147 &mut self,
148 allow_reprocess: bool,
149 gm: &GuestMemory,
150 log_level_override: Option<LogLevel>,
151 log_handler: F,
152 ) -> Result<(), ProcessingError>
153 where
154 F: FnMut(&Log),
155 {
156 if self.processed && !allow_reprocess {
158 tracelimit::warn_ratelimited!("Already processed diagnostics, skipping");
159 return Ok(());
160 }
161
162 self.processed = true;
164
165 let effective_log_level = log_level_override.unwrap_or(self.log_level);
167
168 processor::process_diagnostics_internal(self.gpa, gm, effective_log_level, log_handler)
170 }
171}
172
173pub(crate) enum DiagnosticsEmitter {
175 Tracing { limit: Option<u32> },
177 String,
179}
180
181impl UefiDevice {
182 pub(crate) fn process_diagnostics(
189 &mut self,
190 allow_reprocess: bool,
191 emitter: DiagnosticsEmitter,
192 log_level_override: Option<LogLevel>,
193 ) -> Result<Option<String>, ProcessingError> {
194 use std::fmt::Write;
195 let mut output = match emitter {
196 DiagnosticsEmitter::String => Some(String::new()),
197 DiagnosticsEmitter::Tracing { .. } => None,
198 };
199
200 if let Err(error) = self.service.diagnostics.process_diagnostics(
201 allow_reprocess,
202 &self.gm,
203 log_level_override,
204 |log| {
205 if let Some(out) = &mut output {
206 let _ = writeln!(
207 out,
208 "({} ticks) [{}] [{}]: {}",
209 log.ticks(),
210 log.debug_level_str(),
211 log.phase_str(),
212 log.message_trimmed(),
213 );
214 } else if let DiagnosticsEmitter::Tracing { limit } = emitter {
215 match limit {
216 Some(limit) => emit_log_ratelimited(log, limit),
217 None => emit_log_unrestricted(log),
218 }
219 }
220 },
221 ) {
222 match emitter {
223 DiagnosticsEmitter::Tracing { .. } => {
224 tracelimit::error_ratelimited!(
225 error = &error as &dyn std::error::Error,
226 "failed to process diagnostics buffer"
227 );
228 return Ok(None);
230 }
231 DiagnosticsEmitter::String => return Err(error),
232 }
233 }
234
235 Ok(output)
236 }
237}
238
239mod save_restore {
240 use super::*;
241 use vmcore::save_restore::RestoreError;
242 use vmcore::save_restore::SaveError;
243 use vmcore::save_restore::SaveRestore;
244
245 mod state {
246 use super::LogLevel;
247 use mesh::payload::Protobuf;
248 use vmcore::save_restore::SavedStateRoot;
249
250 #[derive(Protobuf, SavedStateRoot)]
251 #[mesh(package = "firmware.uefi.diagnostics")]
252 pub struct SavedState {
253 #[mesh(1)]
254 pub gpa: Option<u32>,
255 #[mesh(2)]
256 pub did_flush: bool,
257 #[mesh(3)]
258 pub log_level: LogLevel,
259 }
260 }
261
262 impl SaveRestore for DiagnosticsServices {
263 type SavedState = state::SavedState;
264
265 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
266 Ok(state::SavedState {
267 gpa: self.gpa.map(|g| g.get()),
268 did_flush: self.processed,
269 log_level: self.log_level,
270 })
271 }
272
273 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
274 let state::SavedState {
275 gpa,
276 did_flush,
277 log_level,
278 } = state;
279 self.gpa = gpa.and_then(|g| Gpa::new(g).ok());
280 self.processed = did_flush;
281 self.log_level = log_level;
282 Ok(())
283 }
284 }
285}