1#![forbid(unsafe_code)]
14
15mod bios_boot_order;
16mod default_cmos_values;
17mod root_cpu_data;
18
19pub use default_cmos_values::default_cmos_values;
20
21use self::bios_boot_order::bios_boot_order;
22use chipset_device::ChipsetDevice;
23use chipset_device::io::IoError;
24use chipset_device::io::IoResult;
25use chipset_device::io::deferred::DeferredToken;
26use chipset_device::io::deferred::DeferredWrite;
27use chipset_device::io::deferred::defer_write;
28use chipset_device::mmio::MmioIntercept;
29use chipset_device::pio::ControlPortIoIntercept;
30use chipset_device::pio::PortIoIntercept;
31use chipset_device::pio::RegisterPortIoIntercept;
32use chipset_device::poll_device::PollDevice;
33use guestmem::GuestMemory;
34use guestmem::MapRom;
35use guestmem::UnmapRom;
36use inspect::Inspect;
37use inspect::InspectMut;
38use std::fmt::Debug;
39use std::ops::RangeInclusive;
40use std::task::Context;
41use std::time::Duration;
42use thiserror::Error;
43use vm_topology::memory::MemoryLayout;
44use vm_topology::processor::VpIndex;
45use vmcore::device_state::ChangeDeviceState;
46use vmcore::vmtime::VmTimeAccess;
47use vmcore::vmtime::VmTimeSource;
48use zerocopy::IntoBytes;
49
50pub mod config {
52 use guid::Guid;
53 use inspect::Inspect;
54 use memory_range::MemoryRange;
55 use vm_topology::memory::MemoryLayout;
56 use vm_topology::processor::ProcessorTopology;
57 use vm_topology::processor::x86::X86Topology;
58
59 pub const SMBIOS_STRING_MAX_LEN: usize = 8 * 4;
66
67 #[derive(Debug, Inspect)]
69 #[expect(missing_docs)] pub struct SmbiosProcessorInfoBundle {
71 pub processor_family: u8,
72 pub voltage: u8,
73 pub external_clock: u16,
74 pub max_speed: u16,
75 pub current_speed: u16,
76 }
77
78 #[expect(missing_docs)] #[derive(Debug, Inspect)]
84 pub struct SmbiosConstants {
85 pub bios_guid: Guid,
86 #[inspect(with = "String::from_utf8_lossy")]
87 pub system_serial_number: Vec<u8>,
88 #[inspect(with = "String::from_utf8_lossy")]
89 pub base_board_serial_number: Vec<u8>,
90 #[inspect(with = "String::from_utf8_lossy")]
91 pub chassis_serial_number: Vec<u8>,
92 #[inspect(with = "String::from_utf8_lossy")]
93 pub chassis_asset_tag: Vec<u8>,
94 #[inspect(with = "String::from_utf8_lossy")]
95 pub bios_lock_string: Vec<u8>,
96 #[inspect(with = "String::from_utf8_lossy")]
97 pub processor_manufacturer: Vec<u8>,
98 #[inspect(with = "String::from_utf8_lossy")]
99 pub processor_version: Vec<u8>,
100 pub cpu_info_bundle: Option<SmbiosProcessorInfoBundle>,
102 }
103
104 #[derive(Debug, Clone, Copy, Inspect)]
106 #[expect(missing_docs)] pub enum BootDevice {
108 Floppy = 0,
109 Optical = 1,
110 HardDrive = 2,
111 Network = 3,
112 }
113
114 #[derive(Debug, Clone, Copy, Inspect)]
116 pub struct BootDeviceStatus {
117 pub kind: BootDevice,
119 pub attached: bool,
121 }
122
123 #[derive(Debug, Inspect)]
125 pub struct PcatBiosConfig {
126 pub processor_topology: ProcessorTopology<X86Topology>,
128 pub mem_layout: MemoryLayout,
130 pub chipset_low_mmio: MemoryRange,
132 pub chipset_high_mmio: MemoryRange,
134 pub srat: Vec<u8>,
136 pub initial_generation_id: [u8; 16],
138 pub hibernation_enabled: bool,
140 #[inspect(iter_by_index)]
142 pub boot_order: [BootDeviceStatus; 4],
143 pub num_lock_enabled: bool,
145 pub smbios: SmbiosConstants,
147 }
148}
149
150#[derive(Debug)]
152pub enum PcatEvent {
153 BootFailure,
155 BootAttempt,
157}
158
159pub trait PcatLogger: Send {
161 fn log_event(&self, event: PcatEvent);
163}
164
165#[derive(Debug, Inspect)]
166struct PcatBiosState {
167 #[inspect(hex)]
168 address: u32,
169 #[inspect(hex)]
170 read_count: u32,
171 #[inspect(hex)]
172 e820_entry: u8,
173 #[inspect(hex)]
174 srat_offset: u32,
175 #[inspect(hex)]
176 srat_size: u32,
177 #[inspect(hex)]
178 port80: u32,
179 #[inspect(skip)]
180 entropy: [u8; 64],
181 entropy_placed: bool,
182}
183
184impl PcatBiosState {
185 fn new() -> Self {
186 let mut entropy = [0; 64];
187 getrandom::fill(&mut entropy).expect("rng failure");
188 Self {
189 address: 0,
190 read_count: 0,
191 e820_entry: 0,
192 srat_offset: 0,
193 srat_size: 0,
194 port80: 0,
195 entropy,
196 entropy_placed: false,
197 }
198 }
199}
200
201#[expect(missing_docs)] pub struct PcatBiosRuntimeDeps<'a> {
204 pub gm: GuestMemory,
205 pub logger: Box<dyn PcatLogger>,
206 pub generation_id_deps: generation_id::GenerationIdRuntimeDeps,
207 pub vmtime: &'a VmTimeSource,
208 pub rom: Option<Box<dyn MapRom>>,
212 pub register_pio: &'a mut dyn RegisterPortIoIntercept,
213 pub replay_mtrrs: Box<dyn Send + FnMut()>,
215}
216
217#[derive(InspectMut)]
219pub struct PcatBiosDevice {
220 config: config::PcatBiosConfig,
222
223 vmtime_wait: VmTimeAccess,
225 gm: GuestMemory,
226 #[inspect(skip)]
227 logger: Box<dyn PcatLogger>,
228 #[inspect(skip)]
229 _rom_mems: Vec<Box<dyn UnmapRom>>,
230 pre_boot_pio: PreBootStubbedPio,
231 #[inspect(skip)]
232 replay_mtrrs: Box<dyn Send + FnMut()>,
233
234 #[inspect(mut)]
236 generation_id: generation_id::GenerationId,
237
238 #[inspect(skip)]
240 deferred_wait: Option<DeferredWrite>,
241
242 state: PcatBiosState,
244}
245
246const IO_PORT_RANGE_BEGIN: u16 = 0x28;
248const IO_PORT_RANGE_END: u16 = 0x2d;
253const IO_PORT_ADDR_OFFSET: u16 = 0x0;
254const IO_PORT_DATA_OFFSET: u16 = 0x4;
255
256const POST_IO_PORT: u16 = 0x80;
258
259#[derive(Debug, Error)]
261#[expect(missing_docs)] pub enum PcatBiosDeviceInitError {
263 #[error("PCAT requires non-empty chipset low and high MMIO ranges")]
264 IncorrectMmioHoles,
265 #[error("invalid ROM size {0:x} bytes, expected 256KB")]
266 InvalidRomSize(u64),
267 #[error("error mapping ROM")]
268 Rom(#[source] std::io::Error),
269 #[error("SMBIOS {field} of {len} bytes exceeds the config port's {max}-byte limit")]
270 SmbiosStringTooLong {
271 field: &'static str,
272 len: usize,
273 max: usize,
274 },
275}
276
277impl PcatBiosDevice {
278 pub fn new(
280 runtime_deps: PcatBiosRuntimeDeps<'_>,
281 config: config::PcatBiosConfig,
282 ) -> Result<PcatBiosDevice, PcatBiosDeviceInitError> {
283 let PcatBiosRuntimeDeps {
284 gm,
285 logger,
286 generation_id_deps,
287 vmtime,
288 rom,
289 register_pio,
290 replay_mtrrs,
291 } = runtime_deps;
292
293 let initial_generation_id = config.initial_generation_id;
294
295 if config.smbios.system_serial_number.len() > config::SMBIOS_STRING_MAX_LEN {
299 return Err(PcatBiosDeviceInitError::SmbiosStringTooLong {
300 field: "system serial number",
301 len: config.smbios.system_serial_number.len(),
302 max: config::SMBIOS_STRING_MAX_LEN,
303 });
304 }
305
306 if config.chipset_low_mmio.is_empty() || config.chipset_high_mmio.is_empty() {
307 return Err(PcatBiosDeviceInitError::IncorrectMmioHoles);
308 }
309
310 let mut rom_mems = Vec::new();
311 if let Some(rom) = rom {
312 let rom_size = rom.len();
313 if rom_size != 0x40000 {
314 return Err(PcatBiosDeviceInitError::InvalidRomSize(rom_size));
315 }
316
317 for gpa in [0xfffc0000, 0xf0000] {
319 let rom_offset = (gpa + rom_size) & 0xfffff;
320 let len = rom_size - rom_offset;
321 let mem = rom
322 .map_rom(gpa, rom_offset, len)
323 .map_err(PcatBiosDeviceInitError::Rom)?;
324 rom_mems.push(mem);
325 }
326 }
327
328 Ok(PcatBiosDevice {
329 gm,
330 logger,
331 config,
332 state: PcatBiosState::new(),
333 generation_id: generation_id::GenerationId::new(
334 initial_generation_id,
335 generation_id_deps,
336 ),
337 vmtime_wait: vmtime.access("pcat-wait"),
338 deferred_wait: None,
339 _rom_mems: rom_mems,
340 pre_boot_pio: PreBootStubbedPio::new(register_pio),
341 replay_mtrrs,
342 })
343 }
344
345 fn index_using_read_count(&self, data: &[u8]) -> u32 {
346 let index = (self.state.read_count % 8) as usize * 4;
347 let mut buffer = [0u8; 4];
348 for i in 0..4_usize {
349 if index + i < data.len() {
350 buffer[i] = data[index + i];
351 } else {
352 buffer[i] = b' ';
353 }
354 }
355 u32::from_ne_bytes(buffer)
356 }
357
358 fn read_data(&mut self, addr: u32) -> u32 {
359 let mut buffer = [0u8; 4];
360 match PcatAddress(addr) {
361 PcatAddress::FIRST_MEMORY_BLOCK_SIZE => {
362 self.config.mem_layout.ram()[0].range.len().to_kb()
369 }
370 PcatAddress::NUM_LOCK_ENABLED => self.config.num_lock_enabled as u32,
371 PcatAddress::BIOS_GUID => {
372 let index = (self.state.read_count % 4) as usize;
373 buffer.copy_from_slice(&self.config.smbios.bios_guid.as_bytes()[index * 4..][..4]);
374 u32::from_ne_bytes(buffer)
375 }
376 PcatAddress::BIOS_SYSTEM_SERIAL_NUMBER => {
377 self.index_using_read_count(self.config.smbios.system_serial_number.as_bytes())
378 }
379 PcatAddress::BIOS_BASE_SERIAL_NUMBER => {
380 self.index_using_read_count(self.config.smbios.base_board_serial_number.as_bytes())
381 }
382 PcatAddress::BIOS_CHASSIS_SERIAL_NUMBER => {
383 self.index_using_read_count(self.config.smbios.chassis_serial_number.as_bytes())
384 }
385 PcatAddress::BIOS_CHASSIS_ASSET_TAG => {
386 self.index_using_read_count(self.config.smbios.chassis_asset_tag.as_bytes())
387 }
388 PcatAddress::BOOT_DEVICE_ORDER => bios_boot_order(&self.config.boot_order),
389 PcatAddress::BIOS_PROCESSOR_COUNT => self.config.processor_topology.vp_count(),
390 PcatAddress::PROCESSOR_LOCAL_APIC_ID => {
391 if self.state.read_count < self.config.processor_topology.vp_count() {
392 self.config
393 .processor_topology
394 .vp_arch(VpIndex::new(self.state.read_count))
395 .apic_id
396 } else {
397 !0
398 }
399 }
400 PcatAddress::SRAT_SIZE => self.config.srat.len() as u32,
401 PcatAddress::SRAT_DATA => {
402 let srat_chunk = (self.state.srat_offset + self.state.read_count * 4) as usize;
403 if let Some(data) = self.config.srat.get(srat_chunk..).and_then(|c| c.get(..4)) {
404 u32::from_ne_bytes(data.try_into().unwrap())
405 } else {
406 tracelimit::warn_ratelimited!(
407 "invalid SRAT offset: {} + {} * 4 < {} - 4",
408 self.state.srat_offset,
409 self.state.read_count,
410 self.config.srat.len()
411 );
412 0
413 }
414 }
415 PcatAddress::MEMORY_AMOUNT_ABOVE_4GB => {
416 self.config
420 .mem_layout
421 .ram()
422 .iter()
423 .filter(|r| r.range.end() >= 0x1_0000_0000)
424 .map(|r| r.range.len())
425 .sum::<u64>()
426 .to_mb()
427 }
428 PcatAddress::SLEEP_STATES => {
429 if self.config.hibernation_enabled {
438 8
439 } else {
440 0
441 }
442 }
443 PcatAddress::PCI_IO_GAP_START => {
444 self.config.chipset_low_mmio.start().try_into().unwrap()
445 }
446 PcatAddress::PROCESSOR_STA_ENABLE => {
447 if self.state.read_count < self.config.processor_topology.vp_count() {
452 1
453 } else {
454 0
455 }
456 }
457 PcatAddress::BIOS_LOCK_STRING => {
458 self.index_using_read_count(self.config.smbios.bios_lock_string.as_bytes())
459 }
460 PcatAddress::MEMORY_ABOVE_HIGH_MMIO => {
461 self.config
465 .mem_layout
466 .ram()
467 .iter()
468 .filter(|r| r.range.start() >= self.config.chipset_high_mmio.end())
469 .map(|r| r.range.len())
470 .sum::<u64>()
471 .to_mb()
472 }
473 PcatAddress::HIGH_MMIO_GAP_BASE_IN_MB => {
474 self.config.chipset_high_mmio.start().to_mb()
478 }
479 PcatAddress::HIGH_MMIO_GAP_LENGTH_IN_MB => {
480 self.config.chipset_high_mmio.len().to_mb() - 1
490 }
491 PcatAddress::E820_ENTRY => handle_int15_e820_query(
492 &self.config.mem_layout,
493 self.state.e820_entry,
494 self.state.read_count,
495 ),
496 PcatAddress::INITIAL_MEGABYTES_BELOW_GAP => {
497 self.config
499 .mem_layout
500 .ram()
501 .iter()
502 .filter(|r| r.range.end() < 0x1_0000_0000)
503 .map(|r| r.range.len())
504 .sum::<u64>()
505 .to_mb()
506 }
507 _ => {
508 tracelimit::warn_ratelimited!(?addr, "unknown bios read");
509 0xffffffff
510 }
511 }
512 }
513
514 fn write_data(
515 &mut self,
516 addr: u32,
517 data: u32,
518 ) -> Result<Option<DeferredToken>, guestmem::GuestMemoryError> {
519 match PcatAddress(addr) {
520 PcatAddress::BIOS_PROCESSOR_COUNT => {
521 }
523 PcatAddress::SRAT_SIZE => {
524 if self.config.srat.len() > (data as usize) {
525 tracelimit::warn_ratelimited!(
526 data,
527 len = self.config.srat.len(),
528 "improper SRAT_SIZE write",
529 );
530 }
531
532 self.state.srat_size = data;
533 }
534 PcatAddress::SRAT_OFFSET => {
535 if (data as usize) >= self.config.srat.len() || data >= self.state.srat_size {
536 tracelimit::warn_ratelimited!(
537 data,
538 len = self.config.srat.len(),
539 "improper SRAT_OFFSET write",
540 );
541 }
542
543 self.state.srat_offset = data;
544 }
545 PcatAddress::SRAT_DATA => {
546 if data == 0 || data == 0xffffffff {
547 tracelimit::warn_ratelimited!(data, "improper SRAT_DATA write");
548 }
549
550 self.gm.write_at(data as u64, &self.config.srat)?;
551 }
552 PcatAddress::BOOT_FINALIZE => {
553 (self.replay_mtrrs)();
555 }
556 PcatAddress::ENTROPY_TABLE => {
557 if data == 0 || data == 0xffffffff {
558 tracelimit::warn_ratelimited!(data, "improper ENTROPY_TABLE write");
559 }
560
561 if !self.state.entropy_placed {
562 self.gm.write_plain(data as u64, &self.state.entropy)?;
563 self.state.entropy_placed = true;
564 }
565 }
566 PcatAddress::PROCESSOR_DMTF_TABLE => {
567 if data == 0 || data == 0xffffffff {
568 tracelimit::warn_ratelimited!(data, "improper PROCESSOR_DMTF_TABLE write");
569 }
570
571 let cpu_info_legacy = root_cpu_data::get_vp_dmi_info(
572 self.config.smbios.cpu_info_bundle.as_ref(),
573 &self.config.smbios.processor_manufacturer,
574 &self.config.smbios.processor_version,
575 );
576
577 self.gm.write_plain(data as u64, &cpu_info_legacy)?;
578 }
579 PcatAddress::PROCESSOR_STA_ENABLE => {
580 self.state.read_count = data;
583 }
584 PcatAddress::WAIT_NANO100 => {
585 return Ok(Some(
586 self.defer_wait(Duration::from_nanos(data as u64 * 100)),
587 ));
588 }
589 PcatAddress::GENERATION_ID_PTR_LOW => self.generation_id.write_generation_id_low(data),
590 PcatAddress::GENERATION_ID_PTR_HIGH => {
591 self.generation_id.write_generation_id_high(data)
592 }
593 PcatAddress::E820_ENTRY => {
594 self.state.e820_entry = data as u8;
595 }
596 _ => tracelimit::warn_ratelimited!(addr, data, "unknown bios write"),
597 }
598
599 Ok(None)
600 }
601
602 fn write_address(&mut self, addr: u32) -> Option<DeferredToken> {
603 self.state.address = addr;
606 self.state.read_count = 0;
607
608 match PcatAddress(addr) {
611 PcatAddress::WAIT1_MILLISECOND => {
612 return Some(self.defer_wait(Duration::from_millis(1)));
613 }
614 PcatAddress::WAIT10_MILLISECONDS => {
615 return Some(self.defer_wait(Duration::from_millis(10)));
616 }
617 PcatAddress::WAIT2_MILLISECOND => {
618 return Some(self.defer_wait(Duration::from_millis(2)));
619 }
620 PcatAddress::REPORT_BOOT_FAILURE => {
621 tracelimit::info_ratelimited!("pcat boot: failure");
622 self.stop_pre_boot_pio();
623 self.logger.log_event(PcatEvent::BootFailure)
624 }
625 PcatAddress::REPORT_BOOT_ATTEMPT => {
626 tracelimit::info_ratelimited!("pcat boot: attempt");
627 self.stop_pre_boot_pio();
628 self.logger.log_event(PcatEvent::BootAttempt)
629 }
630 _ => {}
631 }
632 None
633 }
634
635 fn defer_wait(&mut self, duration: Duration) -> DeferredToken {
636 tracing::trace!(?duration, "deferring wait request");
637 self.vmtime_wait
638 .set_timeout(self.vmtime_wait.now().wrapping_add(duration));
639 let (write, token) = defer_write();
640 self.deferred_wait = Some(write);
641 token
642 }
643
644 fn stop_pre_boot_pio(&mut self) {
648 if self.pre_boot_pio.is_active() {
649 tracing::info!("disabling pre-boot legacy port-io stubs");
650 self.pre_boot_pio.unmap();
651 }
652 }
653}
654
655open_enum::open_enum! {
656 enum PcatAddress: u32 {
658 FIRST_MEMORY_BLOCK_SIZE = 0x00,
659 NUM_LOCK_ENABLED = 0x01,
660 BIOS_GUID = 0x02,
661 BIOS_SYSTEM_SERIAL_NUMBER = 0x03,
662 BIOS_BASE_SERIAL_NUMBER = 0x04,
663 BIOS_CHASSIS_SERIAL_NUMBER = 0x05,
664 BIOS_CHASSIS_ASSET_TAG = 0x06,
665 BOOT_DEVICE_ORDER = 0x07,
666 BIOS_PROCESSOR_COUNT = 0x08,
667 PROCESSOR_LOCAL_APIC_ID = 0x09,
668 SRAT_SIZE = 0x0A,
669 SRAT_OFFSET = 0x0B,
670 SRAT_DATA = 0x0C,
671 MEMORY_AMOUNT_ABOVE_4GB = 0x0D,
672 GENERATION_ID_PTR_LOW = 0x0E,
673 GENERATION_ID_PTR_HIGH = 0x0F,
674 SLEEP_STATES = 0x10,
675
676 PCI_IO_GAP_START = 0x12,
677
678 PROCESSOR_STA_ENABLE = 0x16,
679 WAIT_NANO100 = 0x17,
680 WAIT1_MILLISECOND = 0x18,
681 WAIT10_MILLISECONDS = 0x19,
682 BOOT_FINALIZE = 0x1A,
683 WAIT2_MILLISECOND = 0x1B,
684 BIOS_LOCK_STRING = 0x1C,
685 PROCESSOR_DMTF_TABLE = 0x1D,
686 ENTROPY_TABLE = 0x1E,
687 MEMORY_ABOVE_HIGH_MMIO = 0x1F,
688 HIGH_MMIO_GAP_BASE_IN_MB = 0x20,
689 HIGH_MMIO_GAP_LENGTH_IN_MB = 0x21,
690 E820_ENTRY = 0x22,
691 INITIAL_MEGABYTES_BELOW_GAP = 0x23,
692
693 REPORT_BOOT_FAILURE = 0x3A,
694 REPORT_BOOT_ATTEMPT = 0x3B,
695 }
696}
697
698fn handle_int15_e820_query(mem_layout: &MemoryLayout, e820_entry: u8, read_count: u32) -> u32 {
772 let index = (e820_entry + 1) as usize;
783
784 if e820_entry == 0 && mem_layout.ram().len() == 1 {
788 return 0;
789 }
790
791 let Some(ram) = mem_layout.ram().get(index) else {
792 tracelimit::warn_ratelimited!(?e820_entry, "unexpected e820 entry");
793 return 0;
794 };
795
796 match read_count {
797 0 => {
798 let mut data = 1; data |= if index + 1 != mem_layout.ram().len() {
800 0b100 } else {
802 0 };
804 data |= ram.range.len().to_mb() << 3; data
806 }
807 1 => ram.range.start() as u32,
808 2 => (ram.range.start() >> 32) as u32,
809 _ => {
810 tracelimit::warn_ratelimited!(?read_count, "invalid E820 read count");
811 0
812 }
813 }
814}
815
816impl ChangeDeviceState for PcatBiosDevice {
817 fn start(&mut self) {}
818
819 async fn stop(&mut self) {}
820
821 async fn reset(&mut self) {
822 self.generation_id.reset();
823 self.state = PcatBiosState::new();
824 }
825}
826
827impl ChipsetDevice for PcatBiosDevice {
828 fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
829 Some(self)
830 }
831
832 fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
833 Some(self)
834 }
835
836 fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
837 Some(self)
838 }
839}
840
841impl PollDevice for PcatBiosDevice {
842 fn poll_device(&mut self, cx: &mut Context<'_>) {
843 self.generation_id.poll(cx);
844 while self.vmtime_wait.poll_timeout(cx).is_ready() {
845 if let Some(deferred) = self.deferred_wait.take() {
846 tracing::trace!("releasing deferred wait");
847 deferred.complete();
848 }
849 }
850 }
851}
852
853impl MmioIntercept for PcatBiosDevice {
854 fn mmio_read(&mut self, _addr: u64, _data: &mut [u8]) -> IoResult {
855 tracelimit::error_ratelimited!("firmware should be mapped, should not be visible as MMIO");
856 IoResult::Ok
857 }
858
859 fn mmio_write(&mut self, addr: u64, _data: &[u8]) -> IoResult {
860 match addr {
861 0xf5bea | 0xf5bfa => {
862 }
868 _ => tracelimit::warn_ratelimited!(addr, "unexpected firmware write"),
869 }
870 IoResult::Ok
871 }
872
873 fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
874 &[
875 ("rom-low", 0xf0000..=0xfffff),
876 ("rom-high", 0xfffc_0000..=0xffff_ffff),
877 ]
878 }
879}
880
881impl PortIoIntercept for PcatBiosDevice {
882 fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
883 if io_port == POST_IO_PORT {
884 data.copy_from_slice(&self.state.port80.to_ne_bytes()[..data.len()]);
885 return IoResult::Ok;
886 }
887
888 if self.pre_boot_pio.contains_port(io_port) {
889 tracing::trace!(?io_port, "stubbed pre-boot pio read");
890 data.fill(!0);
891 return IoResult::Ok;
892 }
893
894 if data.len() != 4 {
895 return IoResult::Err(IoError::InvalidAccessSize);
896 }
897
898 let offset = io_port - IO_PORT_RANGE_BEGIN;
899 let v = match offset {
900 IO_PORT_ADDR_OFFSET => self.state.address,
901 IO_PORT_DATA_OFFSET => self.read_data(self.state.address),
902 _ => return IoResult::Err(IoError::InvalidRegister),
903 };
904 data.copy_from_slice(&v.to_ne_bytes());
905
906 tracing::trace!(
907 offset,
908 address = self.state.address,
909 read_count = self.state.read_count,
910 value = v,
911 "bios read",
912 );
913
914 if offset == IO_PORT_DATA_OFFSET {
915 self.state.read_count += 1;
916 }
917
918 IoResult::Ok
919 }
920
921 fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
922 if io_port == POST_IO_PORT {
923 let mut v = [0; 4];
924 v[..data.len()].copy_from_slice(data);
925 let data = u32::from_ne_bytes(v);
926
927 tracing::debug!(data, "pcat boot: checkpoint");
928
929 const AT_END_POST_CHECKPOINT: u32 = 0x50ac;
931 if data == AT_END_POST_CHECKPOINT {
932 self.stop_pre_boot_pio();
933 }
934
935 self.state.port80 = data;
938 return IoResult::Ok;
939 }
940
941 if self.pre_boot_pio.contains_port(io_port) {
942 tracing::trace!(?io_port, ?data, "stubbed pre-boot pio write");
943 return IoResult::Ok;
944 }
945
946 if data.len() != 4 {
947 return IoResult::Err(IoError::InvalidAccessSize);
948 }
949
950 let offset = io_port - IO_PORT_RANGE_BEGIN;
951 let v = u32::from_ne_bytes(data.try_into().unwrap());
952 let r = match offset {
953 IO_PORT_ADDR_OFFSET => Ok(self.write_address(v)),
954 IO_PORT_DATA_OFFSET => self.write_data(self.state.address, v),
955 _ => return IoResult::Err(IoError::InvalidRegister),
956 };
957
958 match r {
959 Ok(Some(token)) => return IoResult::Defer(token),
960 Ok(None) => {}
961 Err(err) => {
962 tracelimit::warn_ratelimited!(
963 error = &err as &dyn std::error::Error,
964 "bios command error"
965 );
966 }
967 }
968
969 tracing::trace!(
970 offset,
971 address = self.state.address,
972 read_count = self.state.read_count,
973 data = v,
974 "bios write",
975 );
976
977 IoResult::Ok
978 }
979
980 fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
981 &[
982 ("pcat_bios", IO_PORT_RANGE_BEGIN..=IO_PORT_RANGE_END),
983 ("post", POST_IO_PORT..=POST_IO_PORT),
985 ]
986 }
987}
988
989trait ConvertBytes {
991 fn to_mb(self) -> u32;
993 fn to_kb(self) -> u32;
995}
996
997impl ConvertBytes for u64 {
998 fn to_mb(self) -> u32 {
999 (self >> 20).try_into().unwrap()
1000 }
1001
1002 fn to_kb(self) -> u32 {
1003 (self >> 10).try_into().unwrap()
1004 }
1005}
1006
1007#[derive(Inspect)]
1015struct PreBootStubbedPio {
1016 #[inspect(iter_by_index)]
1017 ranges: Vec<Box<dyn ControlPortIoIntercept>>,
1018}
1019
1020impl PreBootStubbedPio {
1021 const LEN_PORT: &'static [(u16, u16)] = &[
1022 (1, 0x279), (1, 0xa79), (1, 0x20b), (1, 0x20f), (1, 0x213),
1028 (1, 0x217),
1029 (1, 0x21b),
1030 (1, 0x21f),
1031 (1, 0x223),
1032 (1, 0x227), (2, 0x102),
1035 (2, 0x46e8),
1036 (1, 0xeb),
1038 (1, 0xee),
1040 (1, 0x6f0),
1042 ];
1043
1044 fn new(register_pio: &mut dyn RegisterPortIoIntercept) -> PreBootStubbedPio {
1045 let mut ranges = Vec::new();
1046 for &(len, port) in Self::LEN_PORT {
1047 let mut control = register_pio.new_io_region("legacy-port-stub", len);
1048 control.map(port);
1049 ranges.push(control)
1050 }
1051 PreBootStubbedPio { ranges }
1052 }
1053
1054 fn is_active(&self) -> bool {
1055 !self.ranges.is_empty()
1056 }
1057
1058 fn unmap(&mut self) {
1059 for mut range in self.ranges.drain(..) {
1060 range.unmap()
1061 }
1062 }
1063
1064 fn contains_port(&self, port: u16) -> bool {
1065 if !self.is_active() {
1066 return false;
1067 }
1068
1069 Self::LEN_PORT
1070 .iter()
1071 .any(|&(len, p)| (p..p + len).contains(&port))
1072 }
1073}
1074
1075mod save_restore {
1076 use super::*;
1077 use vmcore::save_restore::RestoreError;
1078 use vmcore::save_restore::SaveError;
1079 use vmcore::save_restore::SaveRestore;
1080
1081 mod state {
1082 use generation_id::GenerationId;
1083 use mesh::payload::Protobuf;
1084 use vmcore::save_restore::SaveRestore;
1085 use vmcore::save_restore::SavedStateRoot;
1086
1087 #[derive(Protobuf, SavedStateRoot)]
1088 #[mesh(package = "firmware.pcat")]
1089 pub struct SavedState {
1090 #[mesh(1)]
1091 pub address: u32,
1092 #[mesh(2)]
1093 pub read_count: u32,
1094 #[mesh(3)]
1095 pub e820_entry: u8,
1096 #[mesh(4)]
1097 pub srat_offset: u32,
1098 #[mesh(5)]
1099 pub srat_size: u32,
1100 #[mesh(6)]
1101 pub port80: u32,
1102 #[mesh(7)]
1103 pub entropy: [u8; 64],
1104 #[mesh(8)]
1105 pub entropy_placed: bool,
1106
1107 #[mesh(9)]
1108 pub genid: <GenerationId as SaveRestore>::SavedState,
1109 }
1110 }
1111
1112 impl SaveRestore for PcatBiosDevice {
1113 type SavedState = state::SavedState;
1114
1115 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
1116 let PcatBiosState {
1117 address,
1118 read_count,
1119 e820_entry,
1120 srat_offset,
1121 srat_size,
1122 port80,
1123 entropy,
1124 entropy_placed,
1125 } = self.state;
1126
1127 let saved_state = state::SavedState {
1128 address,
1129 read_count,
1130 e820_entry,
1131 srat_offset,
1132 srat_size,
1133 port80,
1134 entropy,
1135 entropy_placed,
1136 genid: self.generation_id.save()?,
1137 };
1138
1139 assert!(self.deferred_wait.is_none());
1141
1142 Ok(saved_state)
1143 }
1144
1145 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
1146 let state::SavedState {
1147 address,
1148 read_count,
1149 e820_entry,
1150 srat_offset,
1151 srat_size,
1152 port80,
1153 entropy,
1154 entropy_placed,
1155 genid,
1156 } = state;
1157
1158 self.state = PcatBiosState {
1159 address,
1160 read_count,
1161 e820_entry,
1162 srat_offset,
1163 srat_size,
1164 port80,
1165 entropy,
1166 entropy_placed,
1167 };
1168
1169 self.generation_id.restore(genid)?;
1170
1171 Ok(())
1172 }
1173 }
1174}