1#![forbid(unsafe_code)]
18
19use chipset_resources::battery::BatteryDeviceHandleAArch64;
20use chipset_resources::battery::BatteryDeviceHandleX64;
21use chipset_resources::battery::HostBatteryUpdate;
22use chipset_resources::i8042::I8042DeviceHandle;
23use input_core::MultiplexedInputHandle;
24use missing_dev_resources::MissingDevHandle;
25use serial_16550_resources::Serial16550DeviceHandle;
26use serial_core::resources::DisconnectedSerialBackendHandle;
27use serial_debugcon_resources::SerialDebugconDeviceHandle;
28use serial_pl011_resources::SerialPl011DeviceHandle;
29use std::iter::zip;
30use thiserror::Error;
31use vm_resource::IntoResource;
32use vm_resource::Resource;
33use vm_resource::kind::SerialBackendHandle;
34use vmotherboard::ChipsetDeviceHandle;
35use vmotherboard::options::BaseChipsetManifest;
36
37pub struct VmManifestBuilder {
39 ty: BaseChipsetType,
40 arch: MachineArch,
41 serial: Option<[Option<Resource<SerialBackendHandle>>; 4]>,
42 serial_wait_for_rts: bool,
43 proxy_vga: bool,
44 stub_floppy: bool,
45 battery_status_recv: Option<mesh::Receiver<HostBatteryUpdate>>,
46 framebuffer: bool,
47 guest_watchdog: bool,
48 psp: bool,
49 debugcon: Option<(Resource<SerialBackendHandle>, u16)>,
50}
51
52pub enum BaseChipsetType {
55 HypervGen1,
57 HypervGen2Uefi,
59 HyperVGen2LinuxDirect,
62 HclHost,
68 UnenlightenedLinuxDirect,
70}
71
72#[derive(Debug, Copy, Clone, PartialEq, Eq)]
74pub enum MachineArch {
75 X86_64,
77 Aarch64,
79}
80
81pub struct VmChipsetResult {
83 pub chipset: BaseChipsetManifest,
85 pub chipset_devices: Vec<ChipsetDeviceHandle>,
87}
88
89#[derive(Debug, Error)]
91#[error(transparent)]
92pub struct Error(#[from] ErrorInner);
93
94#[derive(Debug, Error)]
95enum ErrorInner {
96 #[error("unsupported architecture")]
97 UnsupportedArch,
98 #[error("unsupported serial port count")]
99 UnsupportedSerialCount,
100 #[error("unsupported debugcon architecture")]
101 UnsupportedDebugconArch,
102 #[error("wait for RTS not supported with this serial type")]
103 WaitForRtsNotSupported,
104}
105
106impl VmManifestBuilder {
107 pub fn new(ty: BaseChipsetType, arch: MachineArch) -> Self {
110 VmManifestBuilder {
111 ty,
112 arch,
113 serial: None,
114 serial_wait_for_rts: false,
115 proxy_vga: false,
116 stub_floppy: false,
117 battery_status_recv: None,
118 framebuffer: false,
119 guest_watchdog: false,
120 psp: false,
121 debugcon: None,
122 }
123 }
124
125 pub fn with_serial(mut self, serial: [Option<Resource<SerialBackendHandle>>; 4]) -> Self {
134 self.serial = Some(serial);
135 self
136 }
137
138 pub fn with_serial_wait_for_rts(mut self) -> Self {
143 self.serial_wait_for_rts = true;
144 self
145 }
146
147 pub fn with_debugcon(mut self, serial: Resource<SerialBackendHandle>, port: u16) -> Self {
152 self.debugcon = Some((serial, port));
153 self
154 }
155
156 pub fn with_proxy_vga(mut self) -> Self {
161 assert!(matches!(self.ty, BaseChipsetType::HypervGen1));
162 self.proxy_vga = true;
163 self
164 }
165
166 pub fn with_battery(mut self, battery_status_recv: mesh::Receiver<HostBatteryUpdate>) -> Self {
168 self.battery_status_recv = Some(battery_status_recv);
169 self
170 }
171
172 pub fn with_stub_floppy(mut self) -> Self {
180 assert!(matches!(self.ty, BaseChipsetType::HypervGen1));
181 self.stub_floppy = true;
182 self
183 }
184
185 pub fn with_framebuffer(mut self) -> Self {
192 self.framebuffer = true;
193 self
194 }
195
196 pub fn with_guest_watchdog(mut self) -> Self {
198 self.guest_watchdog = true;
199 self
200 }
201
202 pub fn with_psp(mut self) -> Self {
204 self.psp = true;
205 self
206 }
207
208 pub fn build(self) -> Result<VmChipsetResult, Error> {
210 let mut result = VmChipsetResult {
211 chipset_devices: Vec::new(),
212 chipset: BaseChipsetManifest::empty(),
213 };
214
215 if let Some((backend, port)) = self.debugcon {
216 if matches!(self.arch, MachineArch::X86_64) {
217 result.attach_debugcon(port, backend);
218 } else {
219 return Err(ErrorInner::UnsupportedDebugconArch.into());
220 }
221 }
222
223 match self.ty {
224 BaseChipsetType::HypervGen1 => {
225 if self.arch != MachineArch::X86_64 {
226 return Err(Error(ErrorInner::UnsupportedArch));
227 }
228 result.attach_i8042();
229 result.attach_serial_16550(
231 self.serial_wait_for_rts,
232 self.serial.unwrap_or_else(|| [(); 4].map(|_| None)),
233 );
234 result.chipset = BaseChipsetManifest {
235 with_generic_cmos_rtc: false,
236 with_generic_ioapic: true,
237 with_generic_isa_dma: true,
238 with_generic_isa_floppy: false,
239 with_generic_pci_bus: false,
240 with_generic_pic: true,
241 with_generic_pit: true,
242 with_generic_psp: false,
243 with_hyperv_firmware_pcat: true,
244 with_hyperv_firmware_uefi: false,
245 with_hyperv_framebuffer: !self.proxy_vga,
246 with_hyperv_guest_watchdog: false,
247 with_hyperv_ide: true,
248 with_hyperv_power_management: false,
249 with_hyperv_vga: !self.proxy_vga,
250 with_i440bx_host_pci_bridge: true,
251 with_piix4_cmos_rtc: true,
252 with_piix4_pci_bus: true,
253 with_piix4_pci_isa_bridge: true,
254 with_piix4_pci_usb_uhci_stub: true,
255 with_piix4_power_management: true,
256 with_underhill_vga_proxy: self.proxy_vga,
257 with_winbond_super_io_and_floppy_stub: self.stub_floppy,
258 with_winbond_super_io_and_floppy_full: !self.stub_floppy,
259 };
260 result.attach_missing_arch_ports(self.arch, false);
261 if let Some(recv) = self.battery_status_recv {
262 result.attach_battery(self.arch, recv);
263 }
264 }
265 BaseChipsetType::UnenlightenedLinuxDirect => {
266 let is_x86 = matches!(self.arch, MachineArch::X86_64);
267 result.chipset = BaseChipsetManifest {
268 with_generic_cmos_rtc: is_x86,
269 with_generic_ioapic: is_x86,
270 with_generic_isa_dma: false,
271 with_generic_isa_floppy: false,
272 with_generic_pci_bus: is_x86,
273 with_generic_pic: is_x86,
274 with_generic_pit: is_x86,
275 with_generic_psp: self.psp,
276 with_hyperv_firmware_pcat: false,
277 with_hyperv_firmware_uefi: false,
278 with_hyperv_framebuffer: self.framebuffer,
279 with_hyperv_guest_watchdog: self.guest_watchdog,
280 with_hyperv_ide: false,
281 with_hyperv_power_management: is_x86,
282 with_hyperv_vga: false,
283 with_i440bx_host_pci_bridge: false,
284 with_piix4_cmos_rtc: false,
285 with_piix4_pci_bus: false,
286 with_piix4_pci_isa_bridge: false,
287 with_piix4_pci_usb_uhci_stub: false,
288 with_piix4_power_management: false,
289 with_underhill_vga_proxy: false,
290 with_winbond_super_io_and_floppy_stub: false,
291 with_winbond_super_io_and_floppy_full: false,
292 };
293 result
294 .maybe_attach_arch_serial(
295 self.arch,
296 self.serial_wait_for_rts,
297 true,
298 self.serial,
299 )?
300 .attach_missing_arch_ports(self.arch, false);
301 if let Some(recv) = self.battery_status_recv {
302 result.attach_battery(self.arch, recv);
303 }
304 }
305 BaseChipsetType::HypervGen2Uefi | BaseChipsetType::HyperVGen2LinuxDirect => {
306 let is_x86 = matches!(self.arch, MachineArch::X86_64);
307 result.chipset = BaseChipsetManifest {
308 with_generic_cmos_rtc: is_x86,
309 with_generic_ioapic: is_x86,
310 with_generic_isa_dma: false,
311 with_generic_isa_floppy: false,
312 with_generic_pci_bus: false,
313 with_generic_pic: false,
314 with_generic_pit: false,
315 with_generic_psp: self.psp,
316 with_hyperv_firmware_pcat: false,
317 with_hyperv_firmware_uefi: matches!(self.ty, BaseChipsetType::HypervGen2Uefi),
318 with_hyperv_framebuffer: self.framebuffer,
319 with_hyperv_guest_watchdog: self.guest_watchdog,
320 with_hyperv_ide: false,
321 with_hyperv_power_management: is_x86,
322 with_hyperv_vga: false,
323 with_i440bx_host_pci_bridge: false,
324 with_piix4_cmos_rtc: false,
325 with_piix4_pci_bus: false,
326 with_piix4_pci_isa_bridge: false,
327 with_piix4_pci_usb_uhci_stub: false,
328 with_piix4_power_management: false,
329 with_underhill_vga_proxy: false,
330 with_winbond_super_io_and_floppy_stub: false,
331 with_winbond_super_io_and_floppy_full: false,
332 };
333 result
334 .maybe_attach_arch_serial(
335 self.arch,
336 self.serial_wait_for_rts,
337 true,
338 self.serial,
339 )?
340 .attach_missing_arch_ports(self.arch, true);
341 if let Some(recv) = self.battery_status_recv {
342 result.attach_battery(self.arch, recv);
343 }
344 }
345 BaseChipsetType::HclHost => {
346 result.chipset = BaseChipsetManifest {
347 with_hyperv_framebuffer: self.framebuffer,
348 ..BaseChipsetManifest::empty()
349 };
350 result.maybe_attach_arch_serial(
351 self.arch,
352 self.serial_wait_for_rts,
353 false,
354 self.serial,
355 )?;
356 if let Some(recv) = self.battery_status_recv {
357 result.attach_battery(self.arch, recv);
358 }
359 }
360 }
361 Ok(result)
362 }
363}
364
365impl VmChipsetResult {
366 fn attach_i8042(&mut self) -> &mut Self {
367 self.chipset_devices.push(ChipsetDeviceHandle {
368 name: "i8042".to_owned(),
369 resource: I8042DeviceHandle {
370 keyboard_input: MultiplexedInputHandle { elevation: 0 }.into_resource(),
371 }
372 .into_resource(),
373 });
374 self
375 }
376
377 fn attach_battery(
378 &mut self,
379 arch: MachineArch,
380 battery_status_recv: mesh::Receiver<HostBatteryUpdate>,
381 ) -> &mut Self {
382 self.chipset_devices.push(ChipsetDeviceHandle {
383 name: "battery".to_owned(),
384 resource: match arch {
385 MachineArch::X86_64 => BatteryDeviceHandleX64 {
386 battery_status_recv,
387 }
388 .into_resource(),
389 MachineArch::Aarch64 => BatteryDeviceHandleAArch64 {
390 battery_status_recv,
391 }
392 .into_resource(),
393 },
394 });
395
396 self
397 }
398
399 fn maybe_attach_arch_serial(
400 &mut self,
401 arch: MachineArch,
402 wait_for_rts: bool,
403 register_missing: bool,
404 serial: Option<[Option<Resource<SerialBackendHandle>>; 4]>,
405 ) -> Result<&mut Self, ErrorInner> {
406 if let Some(serial) = serial {
407 match arch {
408 MachineArch::X86_64 => {
409 self.attach_serial_16550(wait_for_rts, serial);
410 }
411 MachineArch::Aarch64 => {
412 if wait_for_rts {
413 return Err(ErrorInner::WaitForRtsNotSupported);
414 }
415 self.attach_serial_pl011(serial)?;
416 }
417 }
418 } else if register_missing && arch == MachineArch::X86_64 {
419 self.chipset_devices.push(ChipsetDeviceHandle {
420 name: "missing-serial".to_owned(),
421 resource: MissingDevHandle::new()
422 .claim_pio("com1", 0x3f8..=0x3ff)
423 .claim_pio("com2", 0x2f8..=0x2ff)
424 .claim_pio("com3", 0x3e8..=0x3ef)
425 .claim_pio("com4", 0x2e8..=0x2ef)
426 .into_resource(),
427 });
428 }
429 Ok(self)
430 }
431
432 fn attach_debugcon(&mut self, port: u16, backend: Resource<SerialBackendHandle>) -> &mut Self {
433 self.chipset_devices.push(ChipsetDeviceHandle {
434 name: format!("debugcon-{port:#x?}"),
435 resource: SerialDebugconDeviceHandle { port, io: backend }.into_resource(),
436 });
437 self
438 }
439
440 fn attach_serial_16550(
441 &mut self,
442 wait_for_rts: bool,
443 backends: [Option<Resource<SerialBackendHandle>>; 4],
444 ) -> &mut Self {
445 let mut devices = Serial16550DeviceHandle::com_ports(
446 backends.map(|r| r.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource())),
447 );
448
449 if wait_for_rts {
450 devices = devices.map(|d| Serial16550DeviceHandle {
451 wait_for_rts: true,
452 ..d
453 });
454 }
455
456 self.chipset_devices.extend(
457 zip(
458 ["serial-com1", "serial-com2", "serial-com3", "serial-com4"],
459 devices,
460 )
461 .map(|(name, device)| ChipsetDeviceHandle {
462 name: name.to_string(),
463 resource: device.into_resource(),
464 }),
465 );
466 self
467 }
468
469 fn attach_serial_pl011(
470 &mut self,
471 backends: [Option<Resource<SerialBackendHandle>>; 4],
472 ) -> Result<&mut Self, ErrorInner> {
473 const PL011_SERIAL0_BASE: u64 = 0xEFFEC000;
474 const PL011_SERIAL0_IRQ: u32 = 1;
475 const PL011_SERIAL1_BASE: u64 = 0xEFFEB000;
476 const PL011_SERIAL1_IRQ: u32 = 2;
477
478 let [backend0, backend1, backend2, backend3] = backends;
479 if backend2.is_some() || backend3.is_some() {
480 return Err(ErrorInner::UnsupportedSerialCount);
481 }
482 self.chipset_devices.extend([
483 ChipsetDeviceHandle {
484 name: "com1".to_string(),
485 resource: SerialPl011DeviceHandle {
486 base: PL011_SERIAL0_BASE,
487 irq: PL011_SERIAL0_IRQ,
488 io: backend0.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
489 }
490 .into_resource(),
491 },
492 ChipsetDeviceHandle {
493 name: "com2".to_string(),
494 resource: SerialPl011DeviceHandle {
495 base: PL011_SERIAL1_BASE,
496 irq: PL011_SERIAL1_IRQ,
497 io: backend1.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
498 }
499 .into_resource(),
500 },
501 ]);
502 Ok(self)
503 }
504
505 fn attach_missing_arch_ports(&mut self, arch: MachineArch, pcat_missing: bool) -> &mut Self {
506 if arch != MachineArch::X86_64 {
507 return self;
508 }
509
510 self.chipset_devices.extend([
511 ChipsetDeviceHandle {
513 name: "io-delay-0xed".to_owned(),
514 resource: MissingDevHandle::new()
515 .claim_pio("delay", 0xed..=0xed)
516 .into_resource(),
517 },
518 ChipsetDeviceHandle {
520 name: "missing-vmware-backdoor".to_owned(),
521 resource: MissingDevHandle::new()
522 .claim_pio("backdoor", 0x5658..=0x5659)
523 .into_resource(),
524 },
525 ChipsetDeviceHandle {
527 name: "missing-gameport".to_owned(),
528 resource: MissingDevHandle::new()
529 .claim_pio("gameport", 0x201..=0x201)
530 .into_resource(),
531 },
532 ]);
533
534 if pcat_missing {
535 self.chipset_devices.extend([
536 ChipsetDeviceHandle {
537 name: "missing-pic".to_owned(),
538 resource: MissingDevHandle::new()
539 .claim_pio("primary", 0x20..=0x21)
540 .claim_pio("secondary", 0xa0..=0xa1)
541 .into_resource(),
542 },
543 ChipsetDeviceHandle {
544 name: "missing-pit".to_owned(),
545 resource: MissingDevHandle::new()
546 .claim_pio("main", 0x40..=0x43)
547 .claim_pio("port61", 0x61..=0x61)
548 .into_resource(),
549 },
550 ChipsetDeviceHandle {
551 name: "missing-pci".to_owned(),
552 resource: MissingDevHandle::new()
553 .claim_pio("address", 0xcf8..=0xcfb)
554 .claim_pio("data", 0xcfc..=0xcff)
555 .into_resource(),
556 },
557 ChipsetDeviceHandle {
560 name: "missing-dma".to_owned(),
561 resource: MissingDevHandle::new()
562 .claim_pio("io", 0x87..=0x87)
563 .into_resource(),
564 },
565 ]);
566 }
567 self
568 }
569}