Skip to main content

firmware_uefi/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! UEFI helper device.
5//!
6//! A bespoke virtual device that works in-tandem with the custom Hyper-V UEFI
7//! firmware running within the guest.
8//!
9//! This device is primarily concerned with implementing + exposing the various
10//! runtime services the UEFI code interfaces with.
11//!
12//! NOTE: Unlike Hyper-V's implementation, this device is _not_ responsible for
13//! injecting UEFI config blobs into guest memory (i.e: things like VM topology
14//! information, device enablement info, etc...). That happens _outside_ this
15//! device, as part of VM initialization, in tandem with loading the UEFI image
16//! itself.
17//!
18//! # Crate Structure
19//!
20//! The idea behind this organization is that conceptually, the UEFI device
21//! isn't so much a single unified device, rather, it's a hodge-podge of little
22//! "micro-devices" that all happen to be dispatched via a single pair of ports.
23//!
24//! ### `mod service`:
25//!
26//! The individual UEFI device services themselves.
27//!
28//! What is a service? As a rule of thumb: a service is something that has
29//! one/more [`UefiCommand`]s associated with it.
30//!
31//! Rather than having each service directly handle its own IO port routing, the
32//! top-level `UefiDevice` code in `lib.rs` takes care of that in one central
33//! location. That way, the only thing service implementations needs to expose
34//! is are service-specific "handler" functions.
35//!
36//! e.g: there's no reason for, say, UEFI generation ID services to directly
37//! share state with the UEFI watchdog service, or the event log service. As
38//! such, each is modeled as a separate struct + impl.
39
40#![expect(missing_docs)]
41#![forbid(unsafe_code)]
42
43pub mod resolver;
44#[cfg(feature = "fuzzing")]
45pub mod service;
46#[cfg(not(feature = "fuzzing"))]
47mod service;
48
49use chipset_device::ChipsetDevice;
50use chipset_device::io::IoError;
51use chipset_device::io::IoResult;
52use chipset_device::mmio::MmioIntercept;
53use chipset_device::pio::PortIoIntercept;
54use chipset_device::poll_device::PollDevice;
55use firmware_uefi_resources::LogLevel;
56use firmware_uefi_resources::UefiCommandSet;
57use firmware_uefi_resources::UefiConfig;
58use firmware_uefi_resources::platform::UefiLogger;
59use firmware_uefi_resources::platform::VsmConfig;
60use guestmem::GuestMemory;
61use inspect::InspectMut;
62use local_clock::InspectableLocalClock;
63use pal_async::local::block_on;
64use service::diagnostics::DEFAULT_LOGS_PER_PERIOD;
65use service::diagnostics::WATCHDOG_LOGS_PER_PERIOD;
66use std::convert::TryInto;
67use std::ops::RangeInclusive;
68use std::task::Context;
69use std::task::Poll;
70use thiserror::Error;
71use uefi_nvram_storage::VmmNvramStorage;
72use vmcore::device_state::ChangeDeviceState;
73use vmcore::vmtime::VmTimeSource;
74use watchdog_core::platform::WatchdogPlatform;
75
76#[derive(Debug, Error)]
77pub enum UefiInitError {
78    #[error("nvram setup error")]
79    NvramSetup(#[from] service::nvram::NvramSetupError),
80    #[error("nvram error")]
81    Nvram(#[from] service::nvram::NvramError),
82    #[error("event log error")]
83    EventLog(#[from] service::event_log::EventLogError),
84}
85
86#[derive(InspectMut)]
87struct UefiDeviceServices {
88    nvram: service::nvram::NvramServices,
89    event_log: service::event_log::EventLogServices,
90    uefi_watchdog: service::uefi_watchdog::UefiWatchdogServices,
91    #[inspect(mut)]
92    generation_id: service::generation_id::GenerationIdServices,
93    #[inspect(mut)]
94    time: service::time::TimeServices,
95    diagnostics: service::diagnostics::DiagnosticsServices,
96}
97
98// Begin and end range are inclusive.
99const IO_PORT_RANGE_BEGIN: u16 = 0x28;
100// The device only decodes dword accesses at REGISTER_ADDRESS and REGISTER_DATA,
101// so the top of the data dword (0x2e/0x2f) is left unclaimed for the
102// "missing-superio" device to absorb guest probes of the legacy SuperIO ports.
103const IO_PORT_RANGE_END: u16 = 0x2d;
104const MMIO_RANGE_BEGIN: u64 = 0xeffed000;
105const MMIO_RANGE_END: u64 = 0xeffedfff;
106
107const REGISTER_ADDRESS: u16 = 0x0;
108const REGISTER_DATA: u16 = 0x4;
109
110/// Various runtime objects used by the UEFI device + underlying services.
111pub struct UefiRuntimeDeps<'a> {
112    pub gm: GuestMemory,
113    pub nvram_storage: Box<dyn VmmNvramStorage>,
114    pub logger: Box<dyn UefiLogger>,
115    pub vmtime: &'a VmTimeSource,
116    pub watchdog_platform: Box<dyn WatchdogPlatform>,
117    pub watchdog_recv: mesh::Receiver<()>,
118    pub generation_id_deps: generation_id::GenerationIdRuntimeDeps,
119    pub vsm_config: Option<Box<dyn VsmConfig>>,
120    pub time_source: Box<dyn InspectableLocalClock>,
121}
122
123/// The Hyper-V UEFI services chipset device.
124#[derive(InspectMut)]
125#[inspect(extra = "UefiDevice::inspect_extra")]
126pub struct UefiDevice {
127    // Fixed configuration
128    use_mmio: bool,
129    command_set: UefiCommandSet,
130    /// Overrides the per-period rate limit applied to EfiDiagnostics
131    /// See [`UefiDevice::resolve_rate_limit`] for more information.
132    diagnostics_rate_limit: Option<u32>,
133
134    // Runtime glue
135    gm: GuestMemory,
136
137    // Sub-emulators
138    #[inspect(mut)]
139    service: UefiDeviceServices,
140
141    // Volatile state
142    #[inspect(hex)]
143    address: u32,
144
145    // Receiver for watchdog timeout events
146    #[inspect(skip)]
147    watchdog_recv: mesh::Receiver<()>,
148}
149
150impl UefiDevice {
151    pub(crate) async fn new(
152        runtime_deps: UefiRuntimeDeps<'_>,
153        cfg: UefiConfig,
154        is_restoring: bool,
155    ) -> Result<Self, UefiInitError> {
156        let UefiRuntimeDeps {
157            gm,
158            nvram_storage,
159            logger,
160            vmtime,
161            watchdog_platform,
162            watchdog_recv,
163            generation_id_deps,
164            vsm_config,
165            time_source,
166        } = runtime_deps;
167
168        // Create the UEFI device with the rest of the services.
169        let uefi = UefiDevice {
170            use_mmio: cfg.use_mmio,
171            command_set: cfg.command_set,
172            diagnostics_rate_limit: cfg.diagnostics_rate_limit,
173            address: 0,
174            gm,
175            watchdog_recv,
176            service: UefiDeviceServices {
177                nvram: service::nvram::NvramServices::new(
178                    nvram_storage,
179                    cfg.base_template,
180                    cfg.custom_uefi_json,
181                    cfg.secure_boot,
182                    vsm_config,
183                    is_restoring,
184                )
185                .await?,
186                event_log: service::event_log::EventLogServices::new(logger),
187                uefi_watchdog: service::uefi_watchdog::UefiWatchdogServices::new(
188                    vmtime.access("uefi-watchdog"),
189                    watchdog_platform,
190                    is_restoring,
191                )
192                .await,
193                generation_id: service::generation_id::GenerationIdServices::new(
194                    cfg.initial_generation_id,
195                    generation_id_deps,
196                ),
197                time: service::time::TimeServices::new(time_source),
198                diagnostics: service::diagnostics::DiagnosticsServices::new(
199                    cfg.diagnostics_log_level,
200                ),
201            },
202        };
203
204        Ok(uefi)
205    }
206
207    /// Resolves the effective per-period rate limit for diagnostics emission,
208    /// given a built-in default and the device's optional override.
209    ///
210    /// - override is `None`: use the built-in default.
211    /// - override is `Some(0)`: disable rate limiting entirely.
212    /// - override is `Some(n)`: use `n` as the override limit.
213    fn resolve_rate_limit(&self, default_limit: u32) -> Option<u32> {
214        match self.diagnostics_rate_limit {
215            None => Some(default_limit),
216            Some(0) => None,
217            Some(n) => Some(n),
218        }
219    }
220
221    fn read_data(&mut self, addr: u32) -> u32 {
222        match UefiCommand(addr) {
223            UefiCommand::WATCHDOG_RESOLUTION
224            | UefiCommand::WATCHDOG_CONFIG
225            | UefiCommand::WATCHDOG_COUNT => {
226                let reg = bios_cmd_to_watchdog_register(UefiCommand(addr)).unwrap();
227                self.handle_watchdog_read(reg)
228            }
229            UefiCommand::NFIT_SIZE => 0, // no NFIT
230            _ => {
231                tracelimit::warn_ratelimited!(?addr, "unknown uefi read");
232                !0
233            }
234        }
235    }
236
237    fn write_data(&mut self, addr: u32, data: u32) {
238        match UefiCommand(addr) {
239            UefiCommand::NVRAM => block_on(self.nvram_handle_command(data.into())),
240            UefiCommand::EVENT_LOG_FLUSH => self.event_log_flush(data),
241            UefiCommand::WATCHDOG_RESOLUTION
242            | UefiCommand::WATCHDOG_CONFIG
243            | UefiCommand::WATCHDOG_COUNT => {
244                let reg = bios_cmd_to_watchdog_register(UefiCommand(addr)).unwrap();
245                self.handle_watchdog_write(reg, data)
246            }
247            UefiCommand::GENERATION_ID_PTR_LOW => self.write_generation_id_low(data),
248            UefiCommand::GENERATION_ID_PTR_HIGH => self.write_generation_id_high(data),
249            UefiCommand::CRYPTO => self.crypto_handle_command(data.into()),
250            UefiCommand::BOOT_FINALIZE if self.command_set == UefiCommandSet::X64 => {
251                // We set MTRRs across all processors at load time, so we don't need to do anything here.
252            }
253            UefiCommand::GET_TIME if self.command_set == UefiCommandSet::Aarch64 => {
254                if let Err(err) = self.get_time(data as u64) {
255                    tracelimit::error_ratelimited!(
256                        error = &err as &dyn std::error::Error,
257                        "failed to access memory for GET_TIME"
258                    );
259                }
260            }
261            UefiCommand::SET_TIME if self.command_set == UefiCommandSet::Aarch64 => {
262                if let Err(err) = self.set_time(data as u64) {
263                    tracelimit::error_ratelimited!(
264                        error = &err as &dyn std::error::Error,
265                        "failed to access memory for SET_TIME"
266                    );
267                }
268            }
269            UefiCommand::SET_EFI_DIAGNOSTICS_GPA => {
270                tracelimit::info_ratelimited!(?addr, data, "set gpa for diagnostics");
271                self.service.diagnostics.set_gpa(data)
272            }
273            UefiCommand::PROCESS_EFI_DIAGNOSTICS => {
274                let _ = self.process_diagnostics(
275                    false,
276                    service::diagnostics::DiagnosticsEmitter::Tracing {
277                        limit: self.resolve_rate_limit(DEFAULT_LOGS_PER_PERIOD),
278                    },
279                    None,
280                );
281            }
282            _ => tracelimit::warn_ratelimited!(addr, data, "unknown uefi write"),
283        }
284    }
285
286    /// Extra inspection fields for the UEFI device.
287    fn inspect_extra(&mut self, resp: &mut inspect::Response<'_>) {
288        const USAGE: &str =
289            "Use: inspect -u <default|info|full>,<stdout|tracing> vm/uefi/process_diagnostics";
290
291        resp.field_mut_with("process_diagnostics", |v| {
292            let output = (|| {
293                let value = v?;
294                let (level_str, dest_str) = value.split_once(',').unwrap_or((value, "stdout"));
295
296                let log_level_override = match level_str {
297                    "default" => Some(LogLevel::make_default()),
298                    "info" => Some(LogLevel::make_info()),
299                    "full" => Some(LogLevel::make_full()),
300                    _ => return None,
301                };
302
303                Some(match dest_str {
304                    "stdout" => match self.process_diagnostics(
305                        true,
306                        service::diagnostics::DiagnosticsEmitter::String,
307                        log_level_override,
308                    ) {
309                        Ok(Some(output)) if output.is_empty() => {
310                            "(no diagnostics entries found)".to_string()
311                        }
312                        Ok(Some(output)) => output,
313                        Ok(None) => unreachable!("String emitter should return output"),
314                        Err(error) => format!("error processing diagnostics: {error}"),
315                    },
316                    "tracing" => {
317                        match self.process_diagnostics(
318                            true,
319                            service::diagnostics::DiagnosticsEmitter::Tracing { limit: None },
320                            log_level_override,
321                        ) {
322                            Ok(_) => format!(
323                                "processed diagnostics via tracing \
324                                 (log_level_override: {level_str})"
325                            ),
326                            Err(error) => {
327                                format!("error processing diagnostics: {error}")
328                            }
329                        }
330                    }
331                    _ => return None,
332                })
333            })();
334
335            Result::<_, std::convert::Infallible>::Ok(output.unwrap_or_else(|| USAGE.to_string()))
336        });
337    }
338}
339
340impl ChangeDeviceState for UefiDevice {
341    fn start(&mut self) {}
342
343    async fn stop(&mut self) {}
344
345    async fn reset(&mut self) {
346        self.address = 0;
347
348        self.service.nvram.reset();
349        self.service.event_log.reset();
350        self.service.uefi_watchdog.watchdog.reset();
351        self.service.generation_id.reset();
352        self.service.diagnostics.reset();
353    }
354}
355
356impl ChipsetDevice for UefiDevice {
357    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
358        (!self.use_mmio).then_some(self)
359    }
360
361    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
362        self.use_mmio.then_some(self)
363    }
364
365    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
366        Some(self)
367    }
368}
369
370impl PollDevice for UefiDevice {
371    fn poll_device(&mut self, cx: &mut Context<'_>) {
372        // Poll services
373        self.service.uefi_watchdog.watchdog.poll(cx);
374        self.service.generation_id.poll(cx);
375
376        // Poll watchdog timeout events
377        if let Poll::Ready(Ok(())) = self.watchdog_recv.poll_recv(cx) {
378            // NOTE: Do not allow reprocessing diagnostics here.
379            // UEFI programs the watchdog's configuration, so we should assume that
380            // this path could trigger multiple times.
381            let _ = self.process_diagnostics(
382                false,
383                service::diagnostics::DiagnosticsEmitter::Tracing {
384                    limit: self.resolve_rate_limit(WATCHDOG_LOGS_PER_PERIOD),
385                },
386                Some(LogLevel::make_info()),
387            );
388        }
389    }
390}
391
392impl PortIoIntercept for UefiDevice {
393    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
394        if data.len() != 4 {
395            return IoResult::Err(IoError::InvalidAccessSize);
396        }
397
398        let offset = io_port - IO_PORT_RANGE_BEGIN;
399
400        let v = match offset {
401            REGISTER_ADDRESS => self.address,
402            REGISTER_DATA => self.read_data(self.address),
403            _ => return IoResult::Err(IoError::InvalidRegister),
404        };
405
406        data.copy_from_slice(&v.to_ne_bytes());
407        IoResult::Ok
408    }
409
410    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
411        if data.len() != 4 {
412            return IoResult::Err(IoError::InvalidAccessSize);
413        }
414
415        let offset = io_port - IO_PORT_RANGE_BEGIN;
416
417        let v = u32::from_ne_bytes(data.try_into().unwrap());
418        match offset {
419            REGISTER_ADDRESS => {
420                self.address = v;
421            }
422            REGISTER_DATA => self.write_data(self.address, v),
423            _ => return IoResult::Err(IoError::InvalidRegister),
424        }
425        IoResult::Ok
426    }
427
428    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
429        &[("uefi", IO_PORT_RANGE_BEGIN..=IO_PORT_RANGE_END)]
430    }
431}
432
433impl MmioIntercept for UefiDevice {
434    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
435        if data.len() != 4 {
436            return IoResult::Err(IoError::InvalidAccessSize);
437        }
438
439        let v = match (addr - MMIO_RANGE_BEGIN) as u16 {
440            REGISTER_ADDRESS => self.address,
441            REGISTER_DATA => self.read_data(self.address),
442            _ => return IoResult::Err(IoError::InvalidRegister),
443        };
444
445        data.copy_from_slice(&v.to_ne_bytes());
446        IoResult::Ok
447    }
448
449    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
450        let Ok(data) = data.try_into() else {
451            return IoResult::Err(IoError::InvalidAccessSize);
452        };
453
454        let v = u32::from_ne_bytes(data);
455        match (addr - MMIO_RANGE_BEGIN) as u16 {
456            REGISTER_ADDRESS => {
457                self.address = v;
458            }
459            REGISTER_DATA => self.write_data(self.address, v),
460            _ => return IoResult::Err(IoError::InvalidRegister),
461        }
462        IoResult::Ok
463    }
464
465    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
466        &[("uefi", MMIO_RANGE_BEGIN..=MMIO_RANGE_END)]
467    }
468}
469
470fn bios_cmd_to_watchdog_register(cmd: UefiCommand) -> Option<watchdog_core::Register> {
471    let res = match cmd {
472        UefiCommand::WATCHDOG_RESOLUTION => watchdog_core::Register::Resolution,
473        UefiCommand::WATCHDOG_CONFIG => watchdog_core::Register::Config,
474        UefiCommand::WATCHDOG_COUNT => watchdog_core::Register::Count,
475        _ => return None,
476    };
477    Some(res)
478}
479
480open_enum::open_enum! {
481    pub enum UefiCommand: u32 {
482        GENERATION_ID_PTR_LOW        = 0x0E,
483        GENERATION_ID_PTR_HIGH       = 0x0F,
484        BOOT_FINALIZE                = 0x1A,
485
486        PROCESSOR_REPLY_STATUS_INDEX = 0x13,
487        PROCESSOR_REPLY_STATUS       = 0x14,
488        PROCESSOR_MAT_ENABLE         = 0x15,
489
490        // Values added in Windows Blue
491        NVRAM                        = 0x24,
492        CRYPTO                       = 0x26,
493
494        // Watchdog device (Windows 8.1 MQ)
495        WATCHDOG_CONFIG              = 0x27,
496        WATCHDOG_RESOLUTION          = 0x28,
497        WATCHDOG_COUNT               = 0x29,
498
499        // EFI Diagnostics
500        SET_EFI_DIAGNOSTICS_GPA      = 0x2B,
501        PROCESS_EFI_DIAGNOSTICS      = 0x2C,
502
503        // Event Logging (Windows 8.1 MQ/M0)
504        EVENT_LOG_FLUSH              = 0x30,
505
506        // Set MOR bit variable. Triggered by TPM _DSM Memory Clear Interface.
507        // In real hardware, _DSM triggers CPU SMM. UEFI SMM driver sets the
508        // MOR state via variable service. Hypervisor does not support virtual SMM,
509        // so _DSM is not able to trigger SMI in Hyper-V virtualization. The
510        // alternative is to send an IO port command to BIOS device and persist the
511        // MOR state in UEFI NVRAM via variable service on host.
512        MOR_SET_VARIABLE             = 0x31,
513
514        // ARM64 RTC GetTime SetTime (RS2)
515        GET_TIME                     = 0x34,
516        SET_TIME                     = 0x35,
517
518        // Debugger output
519        DEBUG_OUTPUT_STRING          = 0x36,
520
521        // vPMem NFIT (RS3)
522        NFIT_SIZE                    = 0x37,
523        NFIT_POPULATE                = 0x38,
524        VPMEM_SET_ACPI_BUFFER        = 0x39,
525    }
526}
527
528mod save_restore {
529    use super::*;
530    use vmcore::save_restore::RestoreError;
531    use vmcore::save_restore::SaveError;
532    use vmcore::save_restore::SaveRestore;
533
534    mod state {
535        use crate::service::diagnostics::DiagnosticsServices;
536        use crate::service::event_log::EventLogServices;
537        use crate::service::generation_id::GenerationIdServices;
538        use crate::service::nvram::NvramServices;
539        use crate::service::time::TimeServices;
540        use crate::service::uefi_watchdog::UefiWatchdogServices;
541        use mesh::payload::Protobuf;
542        use vmcore::save_restore::SaveRestore;
543        use vmcore::save_restore::SavedStateRoot;
544
545        #[derive(Protobuf, SavedStateRoot)]
546        #[mesh(package = "firmware.uefi")]
547        pub struct SavedState {
548            #[mesh(1)]
549            pub address: u32,
550
551            #[mesh(2)]
552            pub nvram: <NvramServices as SaveRestore>::SavedState,
553            #[mesh(3)]
554            pub event_log: <EventLogServices as SaveRestore>::SavedState,
555            #[mesh(4)]
556            pub watchdog: <UefiWatchdogServices as SaveRestore>::SavedState,
557            #[mesh(5)]
558            pub generation_id: <GenerationIdServices as SaveRestore>::SavedState,
559            #[mesh(6)]
560            pub time: <TimeServices as SaveRestore>::SavedState,
561            #[mesh(7)]
562            pub diagnostics: <DiagnosticsServices as SaveRestore>::SavedState,
563        }
564    }
565
566    impl SaveRestore for UefiDevice {
567        type SavedState = state::SavedState;
568
569        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
570            let Self {
571                use_mmio: _,
572                command_set: _,
573                gm: _,
574                watchdog_recv: _,
575                service:
576                    UefiDeviceServices {
577                        nvram,
578                        event_log,
579                        uefi_watchdog,
580                        generation_id,
581                        time,
582                        diagnostics,
583                    },
584                address,
585                diagnostics_rate_limit: _,
586            } = self;
587
588            Ok(state::SavedState {
589                address: *address,
590
591                nvram: nvram.save()?,
592                event_log: event_log.save()?,
593                watchdog: uefi_watchdog.save()?,
594                generation_id: generation_id.save()?,
595                time: time.save()?,
596                diagnostics: diagnostics.save()?,
597            })
598        }
599
600        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
601            let state::SavedState {
602                address,
603
604                nvram,
605                event_log,
606                watchdog,
607                generation_id,
608                time,
609                diagnostics,
610            } = state;
611
612            self.address = address;
613
614            self.service.nvram.restore(nvram)?;
615            self.service.event_log.restore(event_log)?;
616            self.service.uefi_watchdog.restore(watchdog)?;
617            self.service.generation_id.restore(generation_id)?;
618            self.service.time.restore(time)?;
619            self.service.diagnostics.restore(diagnostics)?;
620
621            Ok(())
622        }
623    }
624}