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 (HvLite, 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
59use chipset_device::ChipsetDevice;
60use chipset_device::io::IoError;
61use chipset_device::io::IoResult;
62use chipset_device::mmio::MmioIntercept;
63use chipset_device::pio::PortIoIntercept;
64use chipset_device::poll_device::PollDevice;
65use firmware_uefi_custom_vars::CustomVars;
66use guestmem::GuestMemory;
67use inspect::Inspect;
68use inspect::InspectMut;
69use local_clock::InspectableLocalClock;
70use pal_async::local::block_on;
71use platform::logger::UefiLogger;
72use platform::nvram::VsmConfig;
73use std::convert::TryInto;
74use std::ops::RangeInclusive;
75use std::task::Context;
76use thiserror::Error;
77use uefi_nvram_storage::VmmNvramStorage;
78use vmcore::device_state::ChangeDeviceState;
79use vmcore::vmtime::VmTimeSource;
80use watchdog_core::platform::WatchdogPlatform;
81
82#[derive(Debug, Error)]
83pub enum UefiInitError {
84    #[error("nvram setup error")]
85    NvramSetup(#[from] service::nvram::NvramSetupError),
86    #[error("nvram error")]
87    Nvram(#[from] service::nvram::NvramError),
88    #[error("event log error")]
89    EventLog(#[from] service::event_log::EventLogError),
90}
91
92#[derive(Inspect, PartialEq, Clone)]
93pub enum UefiCommandSet {
94    X64,
95    Aarch64,
96}
97
98#[derive(InspectMut)]
99struct UefiDeviceServices {
100    nvram: service::nvram::NvramServices,
101    event_log: service::event_log::EventLogServices,
102    uefi_watchdog: service::uefi_watchdog::UefiWatchdogServices,
103    #[inspect(mut)]
104    generation_id: service::generation_id::GenerationIdServices,
105    #[inspect(mut)]
106    time: service::time::TimeServices,
107    diagnostics: service::diagnostics::DiagnosticsServices,
108}
109
110// Begin and end range are inclusive.
111const IO_PORT_RANGE_BEGIN: u16 = 0x28;
112const IO_PORT_RANGE_END: u16 = 0x2f;
113const MMIO_RANGE_BEGIN: u64 = 0xeffed000;
114const MMIO_RANGE_END: u64 = 0xeffedfff;
115
116const REGISTER_ADDRESS: u16 = 0x0;
117const REGISTER_DATA: u16 = 0x4;
118
119/// Various bits of static configuration data.
120#[derive(Clone)]
121pub struct UefiConfig {
122    pub custom_uefi_vars: CustomVars,
123    pub secure_boot: bool,
124    pub initial_generation_id: [u8; 16],
125    pub use_mmio: bool,
126    pub command_set: UefiCommandSet,
127}
128
129/// Various runtime objects used by the UEFI device + underlying services.
130pub struct UefiRuntimeDeps<'a> {
131    pub gm: GuestMemory,
132    pub nvram_storage: Box<dyn VmmNvramStorage>,
133    pub logger: Box<dyn UefiLogger>,
134    pub vmtime: &'a VmTimeSource,
135    pub watchdog_platform: Box<dyn WatchdogPlatform>,
136    pub generation_id_deps: generation_id::GenerationIdRuntimeDeps,
137    pub vsm_config: Option<Box<dyn VsmConfig>>,
138    pub time_source: Box<dyn InspectableLocalClock>,
139}
140
141/// The Hyper-V UEFI services chipset device.
142#[derive(InspectMut)]
143pub struct UefiDevice {
144    // Fixed configuration
145    use_mmio: bool,
146    command_set: UefiCommandSet,
147
148    // Runtime glue
149    gm: GuestMemory,
150
151    // Sub-emulators
152    #[inspect(mut)]
153    service: UefiDeviceServices,
154
155    // Volatile state
156    #[inspect(hex)]
157    address: u32,
158}
159
160impl UefiDevice {
161    pub async fn new(
162        runtime_deps: UefiRuntimeDeps<'_>,
163        cfg: UefiConfig,
164        is_restoring: bool,
165    ) -> Result<Self, UefiInitError> {
166        let UefiRuntimeDeps {
167            gm,
168            nvram_storage,
169            logger,
170            vmtime,
171            watchdog_platform,
172            generation_id_deps,
173            vsm_config,
174            time_source,
175        } = runtime_deps;
176
177        let uefi = UefiDevice {
178            use_mmio: cfg.use_mmio,
179            command_set: cfg.command_set,
180            address: 0,
181            gm,
182            service: UefiDeviceServices {
183                nvram: service::nvram::NvramServices::new(
184                    nvram_storage,
185                    cfg.custom_uefi_vars,
186                    cfg.secure_boot,
187                    vsm_config,
188                    is_restoring,
189                )
190                .await?,
191                event_log: service::event_log::EventLogServices::new(logger),
192                uefi_watchdog: service::uefi_watchdog::UefiWatchdogServices::new(
193                    vmtime.access("uefi-watchdog"),
194                    watchdog_platform,
195                    is_restoring,
196                )
197                .await,
198                generation_id: service::generation_id::GenerationIdServices::new(
199                    cfg.initial_generation_id,
200                    generation_id_deps,
201                ),
202                time: service::time::TimeServices::new(time_source),
203                diagnostics: service::diagnostics::DiagnosticsServices::new(),
204            },
205        };
206        Ok(uefi)
207    }
208
209    fn read_data(&mut self, addr: u32) -> u32 {
210        match UefiCommand(addr) {
211            UefiCommand::WATCHDOG_RESOLUTION
212            | UefiCommand::WATCHDOG_CONFIG
213            | UefiCommand::WATCHDOG_COUNT => {
214                let reg = bios_cmd_to_watchdog_register(UefiCommand(addr)).unwrap();
215                self.handle_watchdog_read(reg)
216            }
217            UefiCommand::NFIT_SIZE => 0, // no NFIT
218            _ => {
219                tracelimit::warn_ratelimited!(?addr, "unknown uefi read");
220                !0
221            }
222        }
223    }
224
225    fn write_data(&mut self, addr: u32, data: u32) {
226        match UefiCommand(addr) {
227            UefiCommand::NVRAM => block_on(self.nvram_handle_command(data.into())),
228            UefiCommand::EVENT_LOG_FLUSH => self.event_log_flush(data),
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_write(reg, data)
234            }
235            UefiCommand::GENERATION_ID_PTR_LOW => self.write_generation_id_low(data),
236            UefiCommand::GENERATION_ID_PTR_HIGH => self.write_generation_id_high(data),
237            UefiCommand::CRYPTO => self.crypto_handle_command(data.into()),
238            UefiCommand::BOOT_FINALIZE if self.command_set == UefiCommandSet::X64 => {
239                // We set MTRRs across all processors at load time, so we don't need to do anything here.
240            }
241            UefiCommand::GET_TIME if self.command_set == UefiCommandSet::Aarch64 => {
242                if let Err(err) = self.get_time(data as u64) {
243                    tracelimit::error_ratelimited!(
244                        error = &err as &dyn std::error::Error,
245                        "failed to access memory for GET_TIME"
246                    );
247                }
248            }
249            UefiCommand::SET_TIME if self.command_set == UefiCommandSet::Aarch64 => {
250                if let Err(err) = self.set_time(data as u64) {
251                    tracelimit::error_ratelimited!(
252                        error = &err as &dyn std::error::Error,
253                        "failed to access memory for SET_TIME"
254                    );
255                }
256            }
257            UefiCommand::SET_EFI_DIAGNOSTICS_GPA => self.service.diagnostics.set_gpa(data),
258            UefiCommand::PROCESS_EFI_DIAGNOSTICS => self.process_diagnostics(),
259            _ => tracelimit::warn_ratelimited!(addr, data, "unknown uefi write"),
260        }
261    }
262}
263
264impl ChangeDeviceState for UefiDevice {
265    fn start(&mut self) {}
266
267    async fn stop(&mut self) {}
268
269    async fn reset(&mut self) {
270        self.address = 0;
271
272        self.service.nvram.reset();
273        self.service.event_log.reset();
274        self.service.uefi_watchdog.watchdog.reset();
275        self.service.generation_id.reset();
276    }
277}
278
279impl ChipsetDevice for UefiDevice {
280    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
281        (!self.use_mmio).then_some(self)
282    }
283
284    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
285        self.use_mmio.then_some(self)
286    }
287
288    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
289        Some(self)
290    }
291}
292
293impl PollDevice for UefiDevice {
294    fn poll_device(&mut self, cx: &mut Context<'_>) {
295        self.service.uefi_watchdog.watchdog.poll(cx);
296        self.service.generation_id.poll(cx);
297    }
298}
299
300impl PortIoIntercept for UefiDevice {
301    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
302        if data.len() != 4 {
303            return IoResult::Err(IoError::InvalidAccessSize);
304        }
305
306        let offset = io_port - IO_PORT_RANGE_BEGIN;
307
308        let v = match offset {
309            REGISTER_ADDRESS => self.address,
310            REGISTER_DATA => self.read_data(self.address),
311            _ => return IoResult::Err(IoError::InvalidRegister),
312        };
313
314        data.copy_from_slice(&v.to_ne_bytes());
315        IoResult::Ok
316    }
317
318    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
319        if data.len() != 4 {
320            return IoResult::Err(IoError::InvalidAccessSize);
321        }
322
323        let offset = io_port - IO_PORT_RANGE_BEGIN;
324
325        let v = u32::from_ne_bytes(data.try_into().unwrap());
326        match offset {
327            REGISTER_ADDRESS => {
328                self.address = v;
329            }
330            REGISTER_DATA => self.write_data(self.address, v),
331            _ => return IoResult::Err(IoError::InvalidRegister),
332        }
333        IoResult::Ok
334    }
335
336    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
337        &[("uefi", IO_PORT_RANGE_BEGIN..=IO_PORT_RANGE_END)]
338    }
339}
340
341impl MmioIntercept for UefiDevice {
342    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
343        if data.len() != 4 {
344            return IoResult::Err(IoError::InvalidAccessSize);
345        }
346
347        let v = match (addr - MMIO_RANGE_BEGIN) as u16 {
348            REGISTER_ADDRESS => self.address,
349            REGISTER_DATA => self.read_data(self.address),
350            _ => return IoResult::Err(IoError::InvalidRegister),
351        };
352
353        data.copy_from_slice(&v.to_ne_bytes());
354        IoResult::Ok
355    }
356
357    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
358        let Ok(data) = data.try_into() else {
359            return IoResult::Err(IoError::InvalidAccessSize);
360        };
361
362        let v = u32::from_ne_bytes(data);
363        match (addr - MMIO_RANGE_BEGIN) as u16 {
364            REGISTER_ADDRESS => {
365                self.address = v;
366            }
367            REGISTER_DATA => self.write_data(self.address, v),
368            _ => return IoResult::Err(IoError::InvalidRegister),
369        }
370        IoResult::Ok
371    }
372
373    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
374        &[("uefi", MMIO_RANGE_BEGIN..=MMIO_RANGE_END)]
375    }
376}
377
378fn bios_cmd_to_watchdog_register(cmd: UefiCommand) -> Option<watchdog_core::Register> {
379    let res = match cmd {
380        UefiCommand::WATCHDOG_RESOLUTION => watchdog_core::Register::Resolution,
381        UefiCommand::WATCHDOG_CONFIG => watchdog_core::Register::Config,
382        UefiCommand::WATCHDOG_COUNT => watchdog_core::Register::Count,
383        _ => return None,
384    };
385    Some(res)
386}
387
388open_enum::open_enum! {
389    pub enum UefiCommand: u32 {
390        GENERATION_ID_PTR_LOW        = 0x0E,
391        GENERATION_ID_PTR_HIGH       = 0x0F,
392        BOOT_FINALIZE                = 0x1A,
393
394        PROCESSOR_REPLY_STATUS_INDEX = 0x13,
395        PROCESSOR_REPLY_STATUS       = 0x14,
396        PROCESSOR_MAT_ENABLE         = 0x15,
397
398        // Values added in Windows Blue
399        NVRAM                        = 0x24,
400        CRYPTO                       = 0x26,
401
402        // Watchdog device (Windows 8.1 MQ)
403        WATCHDOG_CONFIG              = 0x27,
404        WATCHDOG_RESOLUTION          = 0x28,
405        WATCHDOG_COUNT               = 0x29,
406
407        // EFI Diagnostics
408        SET_EFI_DIAGNOSTICS_GPA      = 0x2B,
409        PROCESS_EFI_DIAGNOSTICS      = 0x2C,
410
411        // Event Logging (Windows 8.1 MQ/M0)
412        EVENT_LOG_FLUSH              = 0x30,
413
414        // Set MOR bit variable. Triggered by TPM _DSM Memory Clear Interface.
415        // In real hardware, _DSM triggers CPU SMM. UEFI SMM driver sets the
416        // MOR state via variable service. Hypervisor does not support virtual SMM,
417        // so _DSM is not able to trigger SMI in Hyper-V virtualization. The
418        // alternative is to send an IO port command to BIOS device and persist the
419        // MOR state in UEFI NVRAM via variable service on host.
420        MOR_SET_VARIABLE             = 0x31,
421
422        // ARM64 RTC GetTime SetTime (RS2)
423        GET_TIME                     = 0x34,
424        SET_TIME                     = 0x35,
425
426        // Debugger output
427        DEBUG_OUTPUT_STRING          = 0x36,
428
429        // vPMem NFIT (RS3)
430        NFIT_SIZE                    = 0x37,
431        NFIT_POPULATE                = 0x38,
432        VPMEM_SET_ACPI_BUFFER        = 0x39,
433    }
434}
435
436mod save_restore {
437    use super::*;
438    use vmcore::save_restore::RestoreError;
439    use vmcore::save_restore::SaveError;
440    use vmcore::save_restore::SaveRestore;
441
442    mod state {
443        use crate::service::diagnostics::DiagnosticsServices;
444        use crate::service::event_log::EventLogServices;
445        use crate::service::generation_id::GenerationIdServices;
446        use crate::service::nvram::NvramServices;
447        use crate::service::time::TimeServices;
448        use crate::service::uefi_watchdog::UefiWatchdogServices;
449        use mesh::payload::Protobuf;
450        use vmcore::save_restore::SaveRestore;
451        use vmcore::save_restore::SavedStateRoot;
452
453        #[derive(Protobuf, SavedStateRoot)]
454        #[mesh(package = "firmware.uefi")]
455        pub struct SavedState {
456            #[mesh(1)]
457            pub address: u32,
458
459            #[mesh(2)]
460            pub nvram: <NvramServices as SaveRestore>::SavedState,
461            #[mesh(3)]
462            pub event_log: <EventLogServices as SaveRestore>::SavedState,
463            #[mesh(4)]
464            pub watchdog: <UefiWatchdogServices as SaveRestore>::SavedState,
465            #[mesh(5)]
466            pub generation_id: <GenerationIdServices as SaveRestore>::SavedState,
467            #[mesh(6)]
468            pub time: <TimeServices as SaveRestore>::SavedState,
469            #[mesh(7)]
470            pub diagnostics: <DiagnosticsServices as SaveRestore>::SavedState,
471        }
472    }
473
474    impl SaveRestore for UefiDevice {
475        type SavedState = state::SavedState;
476
477        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
478            let Self {
479                use_mmio: _,
480                command_set: _,
481                gm: _,
482                service:
483                    UefiDeviceServices {
484                        nvram,
485                        event_log,
486                        uefi_watchdog,
487                        generation_id,
488                        time,
489                        diagnostics,
490                    },
491                address,
492            } = self;
493
494            Ok(state::SavedState {
495                address: *address,
496
497                nvram: nvram.save()?,
498                event_log: event_log.save()?,
499                watchdog: uefi_watchdog.save()?,
500                generation_id: generation_id.save()?,
501                time: time.save()?,
502                diagnostics: diagnostics.save()?,
503            })
504        }
505
506        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
507            let state::SavedState {
508                address,
509
510                nvram,
511                event_log,
512                watchdog,
513                generation_id,
514                time,
515                diagnostics,
516            } = state;
517
518            self.address = address;
519
520            self.service.nvram.restore(nvram)?;
521            self.service.event_log.restore(event_log)?;
522            self.service.uefi_watchdog.restore(watchdog)?;
523            self.service.generation_id.restore(generation_id)?;
524            self.service.time.restore(time)?;
525            self.service.diagnostics.restore(diagnostics)?;
526
527            Ok(())
528        }
529    }
530}