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