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