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 service::diagnostics::DEFAULT_LOGS_PER_PERIOD;
74use std::convert::TryInto;
75use std::ops::RangeInclusive;
76use std::task::Context;
77use std::task::Poll;
78use thiserror::Error;
79use uefi_nvram_storage::VmmNvramStorage;
80use vmcore::device_state::ChangeDeviceState;
81use vmcore::vmtime::VmTimeSource;
82use watchdog_core::platform::WatchdogPlatform;
83
84#[derive(Debug, Error)]
85pub enum UefiInitError {
86    #[error("nvram setup error")]
87    NvramSetup(#[from] service::nvram::NvramSetupError),
88    #[error("nvram error")]
89    Nvram(#[from] service::nvram::NvramError),
90    #[error("event log error")]
91    EventLog(#[from] service::event_log::EventLogError),
92}
93
94#[derive(Inspect, PartialEq, Clone)]
95pub enum UefiCommandSet {
96    X64,
97    Aarch64,
98}
99
100#[derive(InspectMut)]
101struct UefiDeviceServices {
102    nvram: service::nvram::NvramServices,
103    event_log: service::event_log::EventLogServices,
104    uefi_watchdog: service::uefi_watchdog::UefiWatchdogServices,
105    #[inspect(mut)]
106    generation_id: service::generation_id::GenerationIdServices,
107    #[inspect(mut)]
108    time: service::time::TimeServices,
109    diagnostics: service::diagnostics::DiagnosticsServices,
110}
111
112// Begin and end range are inclusive.
113const IO_PORT_RANGE_BEGIN: u16 = 0x28;
114const IO_PORT_RANGE_END: u16 = 0x2f;
115const MMIO_RANGE_BEGIN: u64 = 0xeffed000;
116const MMIO_RANGE_END: u64 = 0xeffedfff;
117
118const REGISTER_ADDRESS: u16 = 0x0;
119const REGISTER_DATA: u16 = 0x4;
120
121/// Various bits of static configuration data.
122#[derive(Clone)]
123pub struct UefiConfig {
124    pub custom_uefi_vars: CustomVars,
125    pub secure_boot: bool,
126    pub initial_generation_id: [u8; 16],
127    pub use_mmio: bool,
128    pub command_set: UefiCommandSet,
129}
130
131/// Various runtime objects used by the UEFI device + underlying services.
132pub struct UefiRuntimeDeps<'a> {
133    pub gm: GuestMemory,
134    pub nvram_storage: Box<dyn VmmNvramStorage>,
135    pub logger: Box<dyn UefiLogger>,
136    pub vmtime: &'a VmTimeSource,
137    pub watchdog_platform: Box<dyn WatchdogPlatform>,
138    pub watchdog_recv: mesh::Receiver<()>,
139    pub generation_id_deps: generation_id::GenerationIdRuntimeDeps,
140    pub vsm_config: Option<Box<dyn VsmConfig>>,
141    pub time_source: Box<dyn InspectableLocalClock>,
142}
143
144/// The Hyper-V UEFI services chipset device.
145#[derive(InspectMut)]
146#[inspect(extra = "UefiDevice::inspect_extra")]
147pub struct UefiDevice {
148    // Fixed configuration
149    use_mmio: bool,
150    command_set: UefiCommandSet,
151
152    // Runtime glue
153    gm: GuestMemory,
154
155    // Sub-emulators
156    #[inspect(mut)]
157    service: UefiDeviceServices,
158
159    // Volatile state
160    #[inspect(hex)]
161    address: u32,
162
163    // Receiver for watchdog timeout events
164    #[inspect(skip)]
165    watchdog_recv: mesh::Receiver<()>,
166}
167
168impl UefiDevice {
169    pub async fn new(
170        runtime_deps: UefiRuntimeDeps<'_>,
171        cfg: UefiConfig,
172        is_restoring: bool,
173    ) -> Result<Self, UefiInitError> {
174        let UefiRuntimeDeps {
175            gm,
176            nvram_storage,
177            logger,
178            vmtime,
179            watchdog_platform,
180            watchdog_recv,
181            generation_id_deps,
182            vsm_config,
183            time_source,
184        } = runtime_deps;
185
186        // Create the UEFI device with the rest of the services.
187        let uefi = UefiDevice {
188            use_mmio: cfg.use_mmio,
189            command_set: cfg.command_set,
190            address: 0,
191            gm,
192            watchdog_recv,
193            service: UefiDeviceServices {
194                nvram: service::nvram::NvramServices::new(
195                    nvram_storage,
196                    cfg.custom_uefi_vars,
197                    cfg.secure_boot,
198                    vsm_config,
199                    is_restoring,
200                )
201                .await?,
202                event_log: service::event_log::EventLogServices::new(logger),
203                uefi_watchdog: service::uefi_watchdog::UefiWatchdogServices::new(
204                    vmtime.access("uefi-watchdog"),
205                    watchdog_platform,
206                    is_restoring,
207                )
208                .await,
209                generation_id: service::generation_id::GenerationIdServices::new(
210                    cfg.initial_generation_id,
211                    generation_id_deps,
212                ),
213                time: service::time::TimeServices::new(time_source),
214                diagnostics: service::diagnostics::DiagnosticsServices::new(),
215            },
216        };
217
218        Ok(uefi)
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                self.process_diagnostics(false, Some(DEFAULT_LOGS_PER_PERIOD))
275            }
276            _ => tracelimit::warn_ratelimited!(addr, data, "unknown uefi write"),
277        }
278    }
279
280    /// Extra inspection fields for the UEFI device.
281    fn inspect_extra(&mut self, resp: &mut inspect::Response<'_>) {
282        resp.field_mut_with("process_diagnostics", |v| {
283            // NOTE: Today, the inspect source code will fail if we invoke like below
284            // `inspect -u vm/uefi/process_diagnostics`. This is true, even for other
285            // mutable paths in the inspect graph.
286            if v.is_some() {
287                self.process_diagnostics(true, None);
288                Result::<_, std::convert::Infallible>::Ok(
289                    "attempted to process diagnostics through inspect".to_string(),
290                )
291            } else {
292                Result::<_, std::convert::Infallible>::Ok(
293                    "Use: inspect -u 1 vm/uefi/process_diagnostics".to_string(),
294                )
295            }
296        });
297    }
298}
299
300impl ChangeDeviceState for UefiDevice {
301    fn start(&mut self) {}
302
303    async fn stop(&mut self) {}
304
305    async fn reset(&mut self) {
306        self.address = 0;
307
308        self.service.nvram.reset();
309        self.service.event_log.reset();
310        self.service.uefi_watchdog.watchdog.reset();
311        self.service.generation_id.reset();
312        self.service.diagnostics.reset();
313    }
314}
315
316impl ChipsetDevice for UefiDevice {
317    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
318        (!self.use_mmio).then_some(self)
319    }
320
321    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
322        self.use_mmio.then_some(self)
323    }
324
325    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
326        Some(self)
327    }
328}
329
330impl PollDevice for UefiDevice {
331    fn poll_device(&mut self, cx: &mut Context<'_>) {
332        // Poll services
333        self.service.uefi_watchdog.watchdog.poll(cx);
334        self.service.generation_id.poll(cx);
335
336        // Poll watchdog timeout events
337        if let Poll::Ready(Ok(())) = self.watchdog_recv.poll_recv(cx) {
338            // NOTE: Do not allow reprocessing diagnostics here.
339            // UEFI programs the watchdog's configuration, so we should assume that
340            // this path could trigger multiple times.
341            self.process_diagnostics(false, Some(DEFAULT_LOGS_PER_PERIOD));
342        }
343    }
344}
345
346impl PortIoIntercept for UefiDevice {
347    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
348        if data.len() != 4 {
349            return IoResult::Err(IoError::InvalidAccessSize);
350        }
351
352        let offset = io_port - IO_PORT_RANGE_BEGIN;
353
354        let v = match offset {
355            REGISTER_ADDRESS => self.address,
356            REGISTER_DATA => self.read_data(self.address),
357            _ => return IoResult::Err(IoError::InvalidRegister),
358        };
359
360        data.copy_from_slice(&v.to_ne_bytes());
361        IoResult::Ok
362    }
363
364    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
365        if data.len() != 4 {
366            return IoResult::Err(IoError::InvalidAccessSize);
367        }
368
369        let offset = io_port - IO_PORT_RANGE_BEGIN;
370
371        let v = u32::from_ne_bytes(data.try_into().unwrap());
372        match offset {
373            REGISTER_ADDRESS => {
374                self.address = v;
375            }
376            REGISTER_DATA => self.write_data(self.address, v),
377            _ => return IoResult::Err(IoError::InvalidRegister),
378        }
379        IoResult::Ok
380    }
381
382    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
383        &[("uefi", IO_PORT_RANGE_BEGIN..=IO_PORT_RANGE_END)]
384    }
385}
386
387impl MmioIntercept for UefiDevice {
388    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
389        if data.len() != 4 {
390            return IoResult::Err(IoError::InvalidAccessSize);
391        }
392
393        let v = match (addr - MMIO_RANGE_BEGIN) as u16 {
394            REGISTER_ADDRESS => self.address,
395            REGISTER_DATA => self.read_data(self.address),
396            _ => return IoResult::Err(IoError::InvalidRegister),
397        };
398
399        data.copy_from_slice(&v.to_ne_bytes());
400        IoResult::Ok
401    }
402
403    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
404        let Ok(data) = data.try_into() else {
405            return IoResult::Err(IoError::InvalidAccessSize);
406        };
407
408        let v = u32::from_ne_bytes(data);
409        match (addr - MMIO_RANGE_BEGIN) as u16 {
410            REGISTER_ADDRESS => {
411                self.address = v;
412            }
413            REGISTER_DATA => self.write_data(self.address, v),
414            _ => return IoResult::Err(IoError::InvalidRegister),
415        }
416        IoResult::Ok
417    }
418
419    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
420        &[("uefi", MMIO_RANGE_BEGIN..=MMIO_RANGE_END)]
421    }
422}
423
424fn bios_cmd_to_watchdog_register(cmd: UefiCommand) -> Option<watchdog_core::Register> {
425    let res = match cmd {
426        UefiCommand::WATCHDOG_RESOLUTION => watchdog_core::Register::Resolution,
427        UefiCommand::WATCHDOG_CONFIG => watchdog_core::Register::Config,
428        UefiCommand::WATCHDOG_COUNT => watchdog_core::Register::Count,
429        _ => return None,
430    };
431    Some(res)
432}
433
434open_enum::open_enum! {
435    pub enum UefiCommand: u32 {
436        GENERATION_ID_PTR_LOW        = 0x0E,
437        GENERATION_ID_PTR_HIGH       = 0x0F,
438        BOOT_FINALIZE                = 0x1A,
439
440        PROCESSOR_REPLY_STATUS_INDEX = 0x13,
441        PROCESSOR_REPLY_STATUS       = 0x14,
442        PROCESSOR_MAT_ENABLE         = 0x15,
443
444        // Values added in Windows Blue
445        NVRAM                        = 0x24,
446        CRYPTO                       = 0x26,
447
448        // Watchdog device (Windows 8.1 MQ)
449        WATCHDOG_CONFIG              = 0x27,
450        WATCHDOG_RESOLUTION          = 0x28,
451        WATCHDOG_COUNT               = 0x29,
452
453        // EFI Diagnostics
454        SET_EFI_DIAGNOSTICS_GPA      = 0x2B,
455        PROCESS_EFI_DIAGNOSTICS      = 0x2C,
456
457        // Event Logging (Windows 8.1 MQ/M0)
458        EVENT_LOG_FLUSH              = 0x30,
459
460        // Set MOR bit variable. Triggered by TPM _DSM Memory Clear Interface.
461        // In real hardware, _DSM triggers CPU SMM. UEFI SMM driver sets the
462        // MOR state via variable service. Hypervisor does not support virtual SMM,
463        // so _DSM is not able to trigger SMI in Hyper-V virtualization. The
464        // alternative is to send an IO port command to BIOS device and persist the
465        // MOR state in UEFI NVRAM via variable service on host.
466        MOR_SET_VARIABLE             = 0x31,
467
468        // ARM64 RTC GetTime SetTime (RS2)
469        GET_TIME                     = 0x34,
470        SET_TIME                     = 0x35,
471
472        // Debugger output
473        DEBUG_OUTPUT_STRING          = 0x36,
474
475        // vPMem NFIT (RS3)
476        NFIT_SIZE                    = 0x37,
477        NFIT_POPULATE                = 0x38,
478        VPMEM_SET_ACPI_BUFFER        = 0x39,
479    }
480}
481
482mod save_restore {
483    use super::*;
484    use vmcore::save_restore::RestoreError;
485    use vmcore::save_restore::SaveError;
486    use vmcore::save_restore::SaveRestore;
487
488    mod state {
489        use crate::service::diagnostics::DiagnosticsServices;
490        use crate::service::event_log::EventLogServices;
491        use crate::service::generation_id::GenerationIdServices;
492        use crate::service::nvram::NvramServices;
493        use crate::service::time::TimeServices;
494        use crate::service::uefi_watchdog::UefiWatchdogServices;
495        use mesh::payload::Protobuf;
496        use vmcore::save_restore::SaveRestore;
497        use vmcore::save_restore::SavedStateRoot;
498
499        #[derive(Protobuf, SavedStateRoot)]
500        #[mesh(package = "firmware.uefi")]
501        pub struct SavedState {
502            #[mesh(1)]
503            pub address: u32,
504
505            #[mesh(2)]
506            pub nvram: <NvramServices as SaveRestore>::SavedState,
507            #[mesh(3)]
508            pub event_log: <EventLogServices as SaveRestore>::SavedState,
509            #[mesh(4)]
510            pub watchdog: <UefiWatchdogServices as SaveRestore>::SavedState,
511            #[mesh(5)]
512            pub generation_id: <GenerationIdServices as SaveRestore>::SavedState,
513            #[mesh(6)]
514            pub time: <TimeServices as SaveRestore>::SavedState,
515            #[mesh(7)]
516            pub diagnostics: <DiagnosticsServices as SaveRestore>::SavedState,
517        }
518    }
519
520    impl SaveRestore for UefiDevice {
521        type SavedState = state::SavedState;
522
523        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
524            let Self {
525                use_mmio: _,
526                command_set: _,
527                gm: _,
528                watchdog_recv: _,
529                service:
530                    UefiDeviceServices {
531                        nvram,
532                        event_log,
533                        uefi_watchdog,
534                        generation_id,
535                        time,
536                        diagnostics,
537                    },
538                address,
539            } = self;
540
541            Ok(state::SavedState {
542                address: *address,
543
544                nvram: nvram.save()?,
545                event_log: event_log.save()?,
546                watchdog: uefi_watchdog.save()?,
547                generation_id: generation_id.save()?,
548                time: time.save()?,
549                diagnostics: diagnostics.save()?,
550            })
551        }
552
553        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
554            let state::SavedState {
555                address,
556
557                nvram,
558                event_log,
559                watchdog,
560                generation_id,
561                time,
562                diagnostics,
563            } = state;
564
565            self.address = address;
566
567            self.service.nvram.restore(nvram)?;
568            self.service.event_log.restore(event_log)?;
569            self.service.uefi_watchdog.restore(watchdog)?;
570            self.service.generation_id.restore(generation_id)?;
571            self.service.time.restore(time)?;
572            self.service.diagnostics.restore(diagnostics)?;
573
574            Ok(())
575        }
576    }
577}