firmware_uefi/service/diagnostics/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! UEFI diagnostics service
5//!
6//! This service handles processing of the EFI diagnostics buffer,
7//! producing friendly logs for any telemetry during the UEFI boot
8//! process.
9//!
10//! The EFI diagnostics buffer follows the specification of Project Mu's
11//! Advanced Logger package, whose relevant types are defined in the Hyper-V
12//! specification within the uefi_specs crate.
13//!
14//! This file specifically should only expose the public API of the service;
15//! internal implementation details should be in submodules.
16
17use crate::UefiDevice;
18use formatting::EfiDiagnosticsLog;
19use formatting::log_diagnostic_ratelimited;
20use formatting::log_diagnostic_unrestricted;
21use guestmem::GuestMemory;
22use inspect::Inspect;
23use mesh::payload::Protobuf;
24use processor::ProcessingError;
25use uefi_specs::hyperv::debug_level::DEBUG_ERROR;
26use uefi_specs::hyperv::debug_level::DEBUG_INFO;
27use uefi_specs::hyperv::debug_level::DEBUG_WARN;
28
29mod formatting;
30mod message_accumulator;
31mod parser;
32mod processor;
33
34/// Default number of EfiDiagnosticsLogs emitted per period
35pub const DEFAULT_LOGS_PER_PERIOD: u32 = 150;
36
37/// Log level configuration - encapsulates a u32 mask where u32::MAX means log everything
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Protobuf)]
39#[mesh(transparent)]
40pub struct LogLevel(u32);
41
42impl LogLevel {
43    /// Create default log level configuration (ERROR and WARN only)
44    pub const fn make_default() -> Self {
45        Self(DEBUG_ERROR | DEBUG_WARN)
46    }
47
48    /// Create info log level configuration (ERROR, WARN, and INFO)
49    pub const fn make_info() -> Self {
50        Self(DEBUG_ERROR | DEBUG_WARN | DEBUG_INFO)
51    }
52
53    /// Create full log level configuration (all levels)
54    pub const fn make_full() -> Self {
55        Self(u32::MAX)
56    }
57
58    /// Checks if a raw debug level should be logged based on this log level configuration
59    pub fn should_log(self, raw_debug_level: u32) -> bool {
60        if self.0 == u32::MAX {
61            true // Log everything
62        } else {
63            (raw_debug_level & self.0) != 0
64        }
65    }
66}
67
68impl Default for LogLevel {
69    fn default() -> Self {
70        Self::make_default()
71    }
72}
73
74impl Inspect for LogLevel {
75    fn inspect(&self, req: inspect::Request<'_>) {
76        let human_readable = formatting::debug_level_to_string(self.0);
77        req.respond()
78            .field("raw_value", self.0)
79            .field("debug_levels", human_readable.as_ref());
80    }
81}
82
83/// Definition of the diagnostics services state
84#[derive(Inspect)]
85pub struct DiagnosticsServices {
86    /// The guest physical address of the diagnostics buffer
87    gpa: Option<u32>,
88    /// Flag indicating if guest-initiated processing has occurred before
89    has_guest_processed_before: bool,
90    /// Log level used for filtering
91    log_level: LogLevel,
92}
93
94impl DiagnosticsServices {
95    /// Create a new instance of the diagnostics services
96    pub fn new(log_level: LogLevel) -> DiagnosticsServices {
97        DiagnosticsServices {
98            gpa: None,
99            has_guest_processed_before: false,
100            log_level,
101        }
102    }
103
104    /// Reset the diagnostics services state
105    pub fn reset(&mut self) {
106        self.gpa = None;
107        self.has_guest_processed_before = false;
108    }
109
110    /// Set the GPA of the diagnostics buffer
111    pub fn set_gpa(&mut self, gpa: u32) {
112        self.gpa = match gpa {
113            0 => None,
114            _ => Some(gpa),
115        }
116    }
117
118    /// Processes diagnostics from guest memory
119    ///
120    /// # Arguments
121    /// * `allow_reprocess` - If true, allows processing even if already processed for guest
122    /// * `gm` - Guest memory to read diagnostics from
123    /// * `log_handler` - Function to handle each parsed log entry
124    fn process_diagnostics<F>(
125        &mut self,
126        allow_reprocess: bool,
127        gm: &GuestMemory,
128        log_handler: F,
129    ) -> Result<(), ProcessingError>
130    where
131        F: FnMut(EfiDiagnosticsLog<'_>, u32),
132    {
133        // Delegate to the processor module
134        processor::process_diagnostics_internal(
135            &mut self.gpa,
136            &mut self.has_guest_processed_before,
137            allow_reprocess,
138            gm,
139            self.log_level,
140            log_handler,
141        )
142    }
143}
144
145impl UefiDevice {
146    /// Processes UEFI diagnostics from guest memory.
147    ///
148    /// When a limit is provided, traces are rate-limited to avoid spam.
149    /// When no limit is provided, traces are unrestricted.
150    ///
151    /// # Arguments
152    /// * `allow_reprocess` - If true, allows processing even if already processed for guest
153    /// * `limit` - Maximum number of logs to process per period, or `None` for no limit
154    pub(crate) fn process_diagnostics(&mut self, allow_reprocess: bool, limit: Option<u32>) {
155        if let Err(error) = self.service.diagnostics.process_diagnostics(
156            allow_reprocess,
157            &self.gm,
158            |log, raw_debug_level| match limit {
159                Some(limit) => log_diagnostic_ratelimited(log, raw_debug_level, limit),
160                None => log_diagnostic_unrestricted(log, raw_debug_level),
161            },
162        ) {
163            tracelimit::error_ratelimited!(
164                error = &error as &dyn std::error::Error,
165                "failed to process diagnostics buffer"
166            );
167        }
168    }
169}
170
171mod save_restore {
172    use super::*;
173    use vmcore::save_restore::RestoreError;
174    use vmcore::save_restore::SaveError;
175    use vmcore::save_restore::SaveRestore;
176
177    mod state {
178        use super::LogLevel;
179        use mesh::payload::Protobuf;
180        use vmcore::save_restore::SavedStateRoot;
181
182        #[derive(Protobuf, SavedStateRoot)]
183        #[mesh(package = "firmware.uefi.diagnostics")]
184        pub struct SavedState {
185            #[mesh(1)]
186            pub gpa: Option<u32>,
187            #[mesh(2)]
188            pub did_flush: bool,
189            #[mesh(3)]
190            pub log_level: LogLevel,
191        }
192    }
193
194    impl SaveRestore for DiagnosticsServices {
195        type SavedState = state::SavedState;
196
197        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
198            Ok(state::SavedState {
199                gpa: self.gpa,
200                did_flush: self.has_guest_processed_before,
201                log_level: self.log_level,
202            })
203        }
204
205        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
206            let state::SavedState {
207                gpa,
208                did_flush,
209                log_level,
210            } = state;
211            self.gpa = gpa;
212            self.has_guest_processed_before = did_flush;
213            self.log_level = log_level;
214            Ok(())
215        }
216    }
217}