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