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
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
109const 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#[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
128pub 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#[derive(InspectMut)]
142pub struct UefiDevice {
143 use_mmio: bool,
145 command_set: UefiCommandSet,
146
147 gm: GuestMemory,
149
150 #[inspect(mut)]
152 service: UefiDeviceServices,
153
154 #[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, _ => {
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 }
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 NVRAM = 0x24,
396 CRYPTO = 0x26,
397
398 WATCHDOG_CONFIG = 0x27,
400 WATCHDOG_RESOLUTION = 0x28,
401 WATCHDOG_COUNT = 0x29,
402
403 EVENT_LOG_FLUSH = 0x30,
405
406 MOR_SET_VARIABLE = 0x31,
413
414 GET_TIME = 0x34,
416 SET_TIME = 0x35,
417
418 DEBUG_OUTPUT_STRING = 0x36,
420
421 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}