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
37fn emit_log_ratelimited(log: &Log, limit: u32) {
43 if log.debug_level & DEBUG_ERROR != 0 {
44 tracelimit::error_ratelimited!(
45 limit: limit,
46 debug_level = %log.debug_level_str(),
47 ticks = log.ticks(),
48 phase = %log.phase_str(),
49 log_message = log.message_trimmed(),
50 "EFI log entry"
51 )
52 } else if log.debug_level & DEBUG_WARN != 0 {
53 tracelimit::warn_ratelimited!(
54 limit: limit,
55 debug_level = %log.debug_level_str(),
56 ticks = log.ticks(),
57 phase = %log.phase_str(),
58 log_message = log.message_trimmed(),
59 "EFI log entry"
60 )
61 } else {
62 tracelimit::info_ratelimited!(
63 limit: limit,
64 debug_level = %log.debug_level_str(),
65 ticks = log.ticks(),
66 phase = %log.phase_str(),
67 log_message = log.message_trimmed(),
68 "EFI log entry"
69 )
70 }
71}
72
73fn emit_log_unrestricted(log: &Log) {
78 if log.debug_level & DEBUG_ERROR != 0 {
79 tracing::error!(
80 debug_level = %log.debug_level_str(),
81 ticks = log.ticks(),
82 phase = %log.phase_str(),
83 log_message = log.message_trimmed(),
84 "EFI log entry"
85 )
86 } else if log.debug_level & DEBUG_WARN != 0 {
87 tracing::warn!(
88 debug_level = %log.debug_level_str(),
89 ticks = log.ticks(),
90 phase = %log.phase_str(),
91 log_message = log.message_trimmed(),
92 "EFI log entry"
93 )
94 } else {
95 tracing::info!(
96 debug_level = %log.debug_level_str(),
97 ticks = log.ticks(),
98 phase = %log.phase_str(),
99 log_message = log.message_trimmed(),
100 "EFI log entry"
101 )
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Protobuf)]
107#[mesh(transparent)]
108pub struct LogLevel(u32);
109
110impl LogLevel {
111 pub const fn make_default() -> Self {
113 Self(DEBUG_ERROR | DEBUG_WARN)
114 }
115
116 pub const fn make_info() -> Self {
118 Self(DEBUG_ERROR | DEBUG_WARN | DEBUG_INFO)
119 }
120
121 pub const fn make_full() -> Self {
123 Self(u32::MAX)
124 }
125
126 pub fn should_log(self, raw_debug_level: u32) -> bool {
128 if self.0 == u32::MAX {
129 true } else {
131 (raw_debug_level & self.0) != 0
132 }
133 }
134}
135
136impl Default for LogLevel {
137 fn default() -> Self {
138 Self::make_default()
139 }
140}
141
142impl Inspect for LogLevel {
143 fn inspect(&self, req: inspect::Request<'_>) {
144 let human_readable = log::debug_level_to_string(self.0);
145 req.respond()
146 .field("raw_value", self.0)
147 .field("debug_levels", human_readable.as_ref());
148 }
149}
150
151#[derive(Inspect)]
153pub struct DiagnosticsServices {
154 gpa: Option<Gpa>,
156 processed: bool,
158 log_level: LogLevel,
160}
161
162impl DiagnosticsServices {
163 pub fn new(log_level: LogLevel) -> DiagnosticsServices {
165 DiagnosticsServices {
166 gpa: None,
167 processed: false,
168 log_level,
169 }
170 }
171
172 pub fn reset(&mut self) {
174 self.gpa = None;
175 self.processed = false;
176 }
177
178 pub fn set_gpa(&mut self, gpa: u32) {
180 self.gpa = Gpa::new(gpa).ok();
181 }
182
183 pub fn process_diagnostics<F>(
190 &mut self,
191 allow_reprocess: bool,
192 gm: &GuestMemory,
193 log_handler: F,
194 ) -> Result<(), ProcessingError>
195 where
196 F: FnMut(&Log),
197 {
198 if self.processed && !allow_reprocess {
200 tracelimit::warn_ratelimited!("Already processed diagnostics, skipping");
201 return Ok(());
202 }
203
204 self.processed = true;
206
207 processor::process_diagnostics_internal(self.gpa, gm, self.log_level, log_handler)
209 }
210}
211
212impl UefiDevice {
213 pub(crate) fn process_diagnostics(&mut self, allow_reprocess: bool, limit: Option<u32>) {
222 if let Err(error) =
223 self.service
224 .diagnostics
225 .process_diagnostics(allow_reprocess, &self.gm, |log| match limit {
226 Some(limit) => emit_log_ratelimited(log, limit),
227 None => emit_log_unrestricted(log),
228 })
229 {
230 tracelimit::error_ratelimited!(
231 error = &error as &dyn std::error::Error,
232 "failed to process diagnostics buffer"
233 );
234 }
235 }
236}
237
238mod save_restore {
239 use super::*;
240 use vmcore::save_restore::RestoreError;
241 use vmcore::save_restore::SaveError;
242 use vmcore::save_restore::SaveRestore;
243
244 mod state {
245 use super::LogLevel;
246 use mesh::payload::Protobuf;
247 use vmcore::save_restore::SavedStateRoot;
248
249 #[derive(Protobuf, SavedStateRoot)]
250 #[mesh(package = "firmware.uefi.diagnostics")]
251 pub struct SavedState {
252 #[mesh(1)]
253 pub gpa: Option<u32>,
254 #[mesh(2)]
255 pub did_flush: bool,
256 #[mesh(3)]
257 pub log_level: LogLevel,
258 }
259 }
260
261 impl SaveRestore for DiagnosticsServices {
262 type SavedState = state::SavedState;
263
264 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
265 Ok(state::SavedState {
266 gpa: self.gpa.map(|g| g.get()),
267 did_flush: self.processed,
268 log_level: self.log_level,
269 })
270 }
271
272 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
273 let state::SavedState {
274 gpa,
275 did_flush,
276 log_level,
277 } = state;
278 self.gpa = gpa.and_then(|g| Gpa::new(g).ok());
279 self.processed = did_flush;
280 self.log_level = log_level;
281 Ok(())
282 }
283 }
284}