1#![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
115const 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#[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
135pub 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#[derive(InspectMut)]
150#[inspect(extra = "UefiDevice::inspect_extra")]
151pub struct UefiDevice {
152 use_mmio: bool,
154 command_set: UefiCommandSet,
155
156 gm: GuestMemory,
158
159 #[inspect(mut)]
161 service: UefiDeviceServices,
162
163 #[inspect(hex)]
165 address: u32,
166
167 #[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 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, _ => {
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 }
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 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 self.service.uefi_watchdog.watchdog.poll(cx);
380 self.service.generation_id.poll(cx);
381
382 if let Poll::Ready(Ok(())) = self.watchdog_recv.poll_recv(cx) {
384 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 NVRAM = 0x24,
500 CRYPTO = 0x26,
501
502 WATCHDOG_CONFIG = 0x27,
504 WATCHDOG_RESOLUTION = 0x28,
505 WATCHDOG_COUNT = 0x29,
506
507 SET_EFI_DIAGNOSTICS_GPA = 0x2B,
509 PROCESS_EFI_DIAGNOSTICS = 0x2C,
510
511 EVENT_LOG_FLUSH = 0x30,
513
514 MOR_SET_VARIABLE = 0x31,
521
522 GET_TIME = 0x34,
524 SET_TIME = 0x35,
525
526 DEBUG_OUTPUT_STRING = 0x36,
528
529 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}