1use std::collections::HashMap;
9use std::fmt::Debug;
10use std::sync::{Arc, Mutex};
11
12#[cfg(target_arch = "x86_64")]
13use acpi_tables::{Aml, aml};
14use kvm_ioctls::IoEventAddress;
15use linux_loader::cmdline as kernel_cmdline;
16#[cfg(target_arch = "x86_64")]
17use log::debug;
18use serde::{Deserialize, Serialize};
19use vm_allocator::AllocPolicy;
20
21use crate::Vm;
22use crate::arch::BOOT_DEVICE_MEM_START;
23#[cfg(target_arch = "aarch64")]
24use crate::arch::{RTC_MEM_START, SERIAL_MEM_START};
25#[cfg(target_arch = "aarch64")]
26use crate::devices::legacy::{RTCDevice, SerialDevice};
27use crate::devices::pseudo::BootTimer;
28use crate::devices::virtio::transport::mmio::MmioTransport;
29use crate::vstate::bus::{Bus, BusError};
30#[cfg(target_arch = "x86_64")]
31use crate::vstate::memory::GuestAddress;
32use crate::vstate::resources::ResourceAllocator;
33
34#[derive(Debug, thiserror::Error, displaydoc::Display)]
36pub enum MmioError {
37 Allocator(#[from] vm_allocator::Error),
39 BusInsert(#[from] BusError),
41 Cmdline(#[from] linux_loader::cmdline::Error),
43 CreateIrq(#[from] std::io::Error),
45 InvalidIrqConfig,
47 RegisterIoEvent(kvm_ioctls::Error),
49 RegisterIrqFd(kvm_ioctls::Error),
51 #[cfg(target_arch = "x86_64")]
52 AmlError(#[from] aml::AmlError),
54}
55
56pub const MMIO_LEN: u64 = 0x1000;
62
63#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
65pub struct MMIODeviceInfo {
66 pub addr: u64,
68 pub len: u64,
70 pub gsi: Option<u32>,
72}
73
74#[cfg(target_arch = "x86_64")]
75fn add_virtio_aml(
76 dsdt_data: &mut Vec<u8>,
77 addr: u64,
78 len: u64,
79 gsi: u32,
80) -> Result<(), aml::AmlError> {
81 let dev_id = gsi - crate::arch::GSI_LEGACY_START;
82 debug!(
83 "acpi: Building AML for VirtIO device _SB_.V{:03}. memory range: {:#010x}:{} gsi: {}",
84 dev_id, addr, len, gsi
85 );
86 aml::Device::new(
87 format!("V{:03}", dev_id).as_str().try_into()?,
88 vec![
89 &aml::Name::new("_HID".try_into()?, &"LNRO0005")?,
90 &aml::Name::new("_UID".try_into()?, &dev_id)?,
91 &aml::Name::new("_CCA".try_into()?, &aml::ONE)?,
92 &aml::Name::new(
93 "_CRS".try_into()?,
94 &aml::ResourceTemplate::new(vec![
95 &aml::Memory32Fixed::new(
96 true,
97 addr.try_into().unwrap(),
98 len.try_into().unwrap(),
99 ),
100 &aml::Interrupt::new(true, true, false, false, gsi),
101 ]),
102 )?,
103 ],
104 )
105 .append_aml_bytes(dsdt_data)
106}
107
108#[derive(Debug, Clone)]
109pub struct MMIODevice<T> {
111 pub(crate) resources: MMIODeviceInfo,
113 pub(crate) inner: Arc<Mutex<T>>,
115}
116
117impl<T> MMIODevice<T> {
118 pub fn inner(&self) -> &Arc<Mutex<T>> {
119 &self.inner
120 }
121}
122
123#[derive(Debug, Default)]
125pub struct MMIODeviceManager {
126 pub(crate) virtio_devices: HashMap<(u32, String), MMIODevice<MmioTransport>>,
128 pub(crate) boot_timer: Option<MMIODevice<BootTimer>>,
130 #[cfg(target_arch = "aarch64")]
131 pub(crate) rtc: Option<MMIODevice<RTCDevice>>,
133 #[cfg(target_arch = "aarch64")]
134 pub(crate) serial: Option<MMIODevice<SerialDevice>>,
136 #[cfg(target_arch = "x86_64")]
137 pub(crate) dsdt_data: Vec<u8>,
145}
146
147impl MMIODeviceManager {
148 pub fn new() -> MMIODeviceManager {
150 Default::default()
151 }
152
153 fn allocate_mmio_resources(
155 &mut self,
156 resource_allocator: &mut ResourceAllocator,
157 irq_count: u32,
158 ) -> Result<MMIODeviceInfo, MmioError> {
159 let gsi = match resource_allocator.allocate_gsi_legacy(irq_count)?[..] {
160 [] => None,
161 [gsi] => Some(gsi),
162 _ => return Err(MmioError::InvalidIrqConfig),
163 };
164
165 let device_info = MMIODeviceInfo {
166 addr: resource_allocator.allocate_32bit_mmio_memory(
167 MMIO_LEN,
168 MMIO_LEN,
169 AllocPolicy::FirstMatch,
170 )?,
171 len: MMIO_LEN,
172 gsi,
173 };
174 Ok(device_info)
175 }
176
177 pub fn register_mmio_virtio(
179 &mut self,
180 vm: &Vm,
181 device_id: String,
182 device: MMIODevice<MmioTransport>,
183 ) -> Result<(), MmioError> {
184 let gsi = device.resources.gsi.ok_or(MmioError::InvalidIrqConfig)?;
187 let identifier;
188 {
189 let mmio_device = device.inner.lock().expect("Poisoned lock");
190 let locked_device = mmio_device.locked_device();
191 let device_type = locked_device.device_type();
192 identifier = (device_type, device_id);
193 let disable_ioevent = locked_device.as_cow_file_engine().is_some();
194 if !disable_ioevent {
195 for (i, queue_evt) in locked_device.queue_events().iter().enumerate() {
196 let io_addr = IoEventAddress::Mmio(
197 device.resources.addr
198 + u64::from(crate::devices::virtio::NOTIFY_REG_OFFSET),
199 );
200 vm.fd()
201 .register_ioevent(queue_evt, &io_addr, u32::try_from(i).unwrap())
202 .map_err(MmioError::RegisterIoEvent)?;
203 }
204 }
205 vm.register_irq(&mmio_device.interrupt.irq_evt, gsi)
206 .map_err(MmioError::RegisterIrqFd)?;
207 }
208
209 vm.common.mmio_bus.insert(
210 device.inner.clone(),
211 device.resources.addr,
212 device.resources.len,
213 )?;
214 self.virtio_devices.insert(identifier, device);
215
216 Ok(())
217 }
218
219 #[cfg(target_arch = "x86_64")]
221 pub fn add_virtio_device_to_cmdline(
222 cmdline: &mut kernel_cmdline::Cmdline,
223 device_info: &MMIODeviceInfo,
224 ) -> Result<(), MmioError> {
225 cmdline
231 .add_virtio_mmio_device(
232 device_info.len,
233 GuestAddress(device_info.addr),
234 device_info.gsi.unwrap(),
235 None,
236 )
237 .map_err(MmioError::Cmdline)
238 }
239
240 pub fn register_mmio_virtio_for_boot(
243 &mut self,
244 vm: &Vm,
245 device_id: String,
246 mmio_device: MmioTransport,
247 _cmdline: &mut kernel_cmdline::Cmdline,
248 ) -> Result<(), MmioError> {
249 let device = MMIODevice {
250 resources: self.allocate_mmio_resources(&mut vm.resource_allocator(), 1)?,
251 inner: Arc::new(Mutex::new(mmio_device)),
252 };
253
254 #[cfg(target_arch = "x86_64")]
255 {
256 Self::add_virtio_device_to_cmdline(_cmdline, &device.resources)?;
257 add_virtio_aml(
258 &mut self.dsdt_data,
259 device.resources.addr,
260 device.resources.len,
261 device.resources.gsi.unwrap(),
264 )?;
265 }
266 self.register_mmio_virtio(vm, device_id, device)?;
267 Ok(())
268 }
269
270 #[cfg(target_arch = "aarch64")]
271 pub fn register_mmio_serial(
274 &mut self,
275 vm: &Vm,
276 serial: Arc<Mutex<SerialDevice>>,
277 device_info_opt: Option<MMIODeviceInfo>,
278 ) -> Result<(), MmioError> {
279 let device_info = if let Some(device_info) = device_info_opt {
282 device_info
283 } else {
284 let gsi = vm.resource_allocator().allocate_gsi_legacy(1)?;
285 MMIODeviceInfo {
286 addr: SERIAL_MEM_START,
287 len: MMIO_LEN,
288 gsi: Some(gsi[0]),
289 }
290 };
291
292 vm.register_irq(
293 serial.lock().expect("Poisoned lock").serial.interrupt_evt(),
294 device_info.gsi.unwrap(),
295 )
296 .map_err(MmioError::RegisterIrqFd)?;
297
298 let device = MMIODevice {
299 resources: device_info,
300 inner: serial,
301 };
302
303 vm.common.mmio_bus.insert(
304 device.inner.clone(),
305 device.resources.addr,
306 device.resources.len,
307 )?;
308
309 self.serial = Some(device);
310 Ok(())
311 }
312
313 #[cfg(target_arch = "aarch64")]
314 pub fn add_mmio_serial_to_cmdline(
318 &self,
319 cmdline: &mut kernel_cmdline::Cmdline,
320 ) -> Result<(), MmioError> {
321 let device = self.serial.as_ref().unwrap();
322 cmdline.insert(
323 "earlycon",
324 &format!("uart,mmio,0x{:08x}", device.resources.addr),
325 )?;
326 Ok(())
327 }
328
329 #[cfg(target_arch = "aarch64")]
330 pub fn register_mmio_rtc(
333 &mut self,
334 vm: &Vm,
335 rtc: Arc<Mutex<RTCDevice>>,
336 device_info_opt: Option<MMIODeviceInfo>,
337 ) -> Result<(), MmioError> {
338 let device_info = if let Some(device_info) = device_info_opt {
341 device_info
342 } else {
343 let gsi = vm.resource_allocator().allocate_gsi_legacy(1)?;
344 MMIODeviceInfo {
345 addr: RTC_MEM_START,
346 len: MMIO_LEN,
347 gsi: Some(gsi[0]),
348 }
349 };
350
351 let device = MMIODevice {
352 resources: device_info,
353 inner: rtc,
354 };
355
356 vm.common.mmio_bus.insert(
357 device.inner.clone(),
358 device.resources.addr,
359 device.resources.len,
360 )?;
361 self.rtc = Some(device);
362 Ok(())
363 }
364
365 pub fn register_mmio_boot_timer(
367 &mut self,
368 mmio_bus: &Bus,
369 boot_timer: Arc<Mutex<BootTimer>>,
370 ) -> Result<(), MmioError> {
371 let device_info = MMIODeviceInfo {
373 addr: BOOT_DEVICE_MEM_START,
374 len: MMIO_LEN,
375 gsi: None,
376 };
377
378 let device = MMIODevice {
379 resources: device_info,
380 inner: boot_timer,
381 };
382
383 mmio_bus.insert(
384 device.inner.clone(),
385 device.resources.addr,
386 device.resources.len,
387 )?;
388 self.boot_timer = Some(device);
389
390 Ok(())
391 }
392
393 pub fn get_virtio_device(
395 &self,
396 virtio_type: u32,
397 device_id: &str,
398 ) -> Option<&MMIODevice<MmioTransport>> {
399 self.virtio_devices
400 .get(&(virtio_type, device_id.to_string()))
401 }
402
403 pub fn for_each_virtio_device<F, E: Debug>(&self, mut f: F) -> Result<(), E>
405 where
406 F: FnMut(&u32, &String, &MMIODevice<MmioTransport>) -> Result<(), E>,
407 {
408 for ((virtio_type, device_id), mmio_device) in &self.virtio_devices {
409 f(virtio_type, device_id, mmio_device)?;
410 }
411 Ok(())
412 }
413
414 #[cfg(target_arch = "aarch64")]
415 pub fn virtio_device_info(&self) -> Vec<&MMIODeviceInfo> {
416 let mut device_info = Vec::new();
417 for (_, dev) in self.virtio_devices.iter() {
418 device_info.push(&dev.resources);
419 }
420 device_info
421 }
422
423 #[cfg(target_arch = "aarch64")]
424 pub fn rtc_device_info(&self) -> Option<&MMIODeviceInfo> {
425 self.rtc.as_ref().map(|device| &device.resources)
426 }
427
428 #[cfg(target_arch = "aarch64")]
429 pub fn serial_device_info(&self) -> Option<&MMIODeviceInfo> {
430 self.serial.as_ref().map(|device| &device.resources)
431 }
432}
433
434#[cfg(test)]
435pub(crate) mod tests {
436
437 use std::ops::Deref;
438 use std::sync::Arc;
439
440 use vmm_sys_util::eventfd::EventFd;
441
442 use super::*;
443 use crate::devices::virtio::ActivateError;
444 use crate::devices::virtio::device::VirtioDevice;
445 use crate::devices::virtio::queue::Queue;
446 use crate::devices::virtio::transport::VirtioInterrupt;
447 use crate::devices::virtio::transport::mmio::IrqTrigger;
448 use crate::test_utils::multi_region_mem_raw;
449 use crate::vstate::kvm::Kvm;
450 use crate::vstate::memory::{GuestAddress, GuestMemoryMmap};
451 use crate::{Vm, arch, impl_device_type};
452
453 const QUEUE_SIZES: &[u16] = &[64];
454
455 impl MMIODeviceManager {
456 pub(crate) fn register_virtio_test_device(
457 &mut self,
458 vm: &Vm,
459 guest_mem: GuestMemoryMmap,
460 device: Arc<Mutex<dyn VirtioDevice>>,
461 cmdline: &mut kernel_cmdline::Cmdline,
462 dev_id: &str,
463 ) -> Result<u64, MmioError> {
464 let interrupt = Arc::new(IrqTrigger::new());
465 let mmio_device = MmioTransport::new(guest_mem, interrupt, device.clone(), false);
466 self.register_mmio_virtio_for_boot(vm, dev_id.to_string(), mmio_device, cmdline)?;
467 Ok(self
468 .get_virtio_device(device.lock().unwrap().device_type(), dev_id)
469 .unwrap()
470 .resources
471 .addr)
472 }
473
474 #[cfg(target_arch = "x86_64")]
475 pub fn used_irqs_count(&self) -> usize {
477 self.virtio_devices
478 .iter()
479 .filter(|(_, mmio_dev)| mmio_dev.resources.gsi.is_some())
480 .count()
481 }
482 }
483
484 #[allow(dead_code)]
485 #[derive(Debug)]
486 pub(crate) struct DummyDevice {
487 dummy: u32,
488 queues: Vec<Queue>,
489 queue_evts: [EventFd; 1],
490 interrupt_trigger: Option<Arc<IrqTrigger>>,
491 }
492
493 impl DummyDevice {
494 pub fn new() -> Self {
495 DummyDevice {
496 dummy: 0,
497 queues: QUEUE_SIZES.iter().map(|&s| Queue::new(s)).collect(),
498 queue_evts: [EventFd::new(libc::EFD_NONBLOCK).expect("cannot create eventFD")],
499 interrupt_trigger: None,
500 }
501 }
502 }
503
504 impl VirtioDevice for DummyDevice {
505 impl_device_type!(0);
506
507 fn avail_features(&self) -> u64 {
508 0
509 }
510
511 fn acked_features(&self) -> u64 {
512 0
513 }
514
515 fn set_acked_features(&mut self, _: u64) {}
516
517 fn queues(&self) -> &[Queue] {
518 &self.queues
519 }
520
521 fn queues_mut(&mut self) -> &mut [Queue] {
522 &mut self.queues
523 }
524
525 fn queue_events(&self) -> &[EventFd] {
526 &self.queue_evts
527 }
528
529 fn interrupt_trigger(&self) -> &dyn VirtioInterrupt {
530 self.interrupt_trigger.as_ref().unwrap().deref()
531 }
532
533 fn ack_features_by_page(&mut self, page: u32, value: u32) {
534 let _ = page;
535 let _ = value;
536 }
537
538 fn read_config(&self, offset: u64, data: &mut [u8]) {
539 let _ = offset;
540 let _ = data;
541 }
542
543 fn write_config(&mut self, offset: u64, data: &[u8]) {
544 let _ = offset;
545 let _ = data;
546 }
547
548 fn activate(
549 &mut self,
550 _: GuestMemoryMmap,
551 _: Arc<dyn VirtioInterrupt>,
552 ) -> Result<(), ActivateError> {
553 Ok(())
554 }
555
556 fn is_activated(&self) -> bool {
557 false
558 }
559 }
560
561 #[test]
562 #[cfg_attr(target_arch = "x86_64", allow(unused_mut))]
563 fn test_register_virtio_device() {
564 let start_addr1 = GuestAddress(0x0);
565 let start_addr2 = GuestAddress(0x1000);
566 let guest_mem = multi_region_mem_raw(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]);
567 let kvm = Kvm::new(vec![]).expect("Cannot create Kvm");
568 let mut vm = Vm::new(&kvm).unwrap();
569 vm.register_dram_memory_regions(guest_mem).unwrap();
570 let mut device_manager = MMIODeviceManager::new();
571
572 let mut cmdline = kernel_cmdline::Cmdline::new(4096).unwrap();
573 let dummy = Arc::new(Mutex::new(DummyDevice::new()));
574 #[cfg(target_arch = "x86_64")]
575 vm.setup_irqchip().unwrap();
576 #[cfg(target_arch = "aarch64")]
577 vm.setup_irqchip(1).unwrap();
578
579 device_manager
580 .register_virtio_test_device(
581 &vm,
582 vm.guest_memory().clone(),
583 dummy,
584 &mut cmdline,
585 "dummy",
586 )
587 .unwrap();
588
589 assert!(device_manager.get_virtio_device(0, "foo").is_none());
590 let dev = device_manager.get_virtio_device(0, "dummy").unwrap();
591 assert_eq!(dev.resources.addr, arch::MEM_32BIT_DEVICES_START);
592 assert_eq!(dev.resources.len, MMIO_LEN);
593 assert_eq!(dev.resources.gsi, Some(arch::GSI_LEGACY_START));
594
595 device_manager
596 .for_each_virtio_device(|virtio_type, device_id, mmio_device| {
597 assert_eq!(*virtio_type, 0);
598 assert_eq!(device_id, "dummy");
599 assert_eq!(mmio_device.resources.addr, arch::MEM_32BIT_DEVICES_START);
600 assert_eq!(mmio_device.resources.len, MMIO_LEN);
601 assert_eq!(mmio_device.resources.gsi, Some(arch::GSI_LEGACY_START));
602 Ok::<(), ()>(())
603 })
604 .unwrap();
605 }
606
607 #[test]
608 #[cfg_attr(target_arch = "x86_64", allow(unused_mut))]
609 fn test_register_too_many_devices() {
610 let start_addr1 = GuestAddress(0x0);
611 let start_addr2 = GuestAddress(0x1000);
612 let guest_mem = multi_region_mem_raw(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]);
613 let kvm = Kvm::new(vec![]).expect("Cannot create Kvm");
614 let mut vm = Vm::new(&kvm).unwrap();
615 vm.register_dram_memory_regions(guest_mem).unwrap();
616 let mut device_manager = MMIODeviceManager::new();
617
618 let mut cmdline = kernel_cmdline::Cmdline::new(4096).unwrap();
619 #[cfg(target_arch = "x86_64")]
620 vm.setup_irqchip().unwrap();
621 #[cfg(target_arch = "aarch64")]
622 vm.setup_irqchip(1).unwrap();
623
624 for _i in crate::arch::GSI_LEGACY_START..=crate::arch::GSI_LEGACY_END {
625 device_manager
626 .register_virtio_test_device(
627 &vm,
628 vm.guest_memory().clone(),
629 Arc::new(Mutex::new(DummyDevice::new())),
630 &mut cmdline,
631 "dummy1",
632 )
633 .unwrap();
634 }
635 assert_eq!(
636 format!(
637 "{}",
638 device_manager
639 .register_virtio_test_device(
640 &vm,
641 vm.guest_memory().clone(),
642 Arc::new(Mutex::new(DummyDevice::new())),
643 &mut cmdline,
644 "dummy2"
645 )
646 .unwrap_err()
647 ),
648 "Failed to allocate requested resource: The requested resource is not available."
649 .to_string()
650 );
651 }
652
653 #[test]
654 fn test_dummy_device() {
655 let dummy = DummyDevice::new();
656 assert_eq!(dummy.device_type(), 0);
657 assert_eq!(dummy.queues().len(), QUEUE_SIZES.len());
658 }
659
660 #[test]
661 #[cfg_attr(target_arch = "x86_64", allow(unused_mut))]
662 fn test_device_info() {
663 let start_addr1 = GuestAddress(0x0);
664 let start_addr2 = GuestAddress(0x1000);
665 let guest_mem = multi_region_mem_raw(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]);
666 let kvm = Kvm::new(vec![]).expect("Cannot create Kvm");
667 let mut vm = Vm::new(&kvm).unwrap();
668 vm.register_dram_memory_regions(guest_mem).unwrap();
669
670 #[cfg(target_arch = "x86_64")]
671 vm.setup_irqchip().unwrap();
672 #[cfg(target_arch = "aarch64")]
673 vm.setup_irqchip(1).unwrap();
674
675 let mut device_manager = MMIODeviceManager::new();
676 let mut cmdline = kernel_cmdline::Cmdline::new(4096).unwrap();
677 let dummy = Arc::new(Mutex::new(DummyDevice::new()));
678
679 let type_id = dummy.lock().unwrap().device_type();
680 let id = String::from("foo");
681 let addr = device_manager
682 .register_virtio_test_device(&vm, vm.guest_memory().clone(), dummy, &mut cmdline, &id)
683 .unwrap();
684 assert!(device_manager.get_virtio_device(type_id, &id).is_some());
685 assert_eq!(
686 addr,
687 device_manager.virtio_devices[&(type_id, id.clone())]
688 .resources
689 .addr
690 );
691 assert_eq!(
692 crate::arch::GSI_LEGACY_START,
693 device_manager.virtio_devices[&(type_id, id)]
694 .resources
695 .gsi
696 .unwrap()
697 );
698
699 let id = "bar";
700 assert!(device_manager.get_virtio_device(type_id, id).is_none());
701
702 let dummy2 = Arc::new(Mutex::new(DummyDevice::new()));
703 let id2 = String::from("foo2");
704 device_manager
705 .register_virtio_test_device(&vm, vm.guest_memory().clone(), dummy2, &mut cmdline, &id2)
706 .unwrap();
707
708 let mut count = 0;
709 let _: Result<(), MmioError> =
710 device_manager.for_each_virtio_device(|devtype, devid, _| {
711 assert_eq!(*devtype, type_id);
712 match devid.as_str() {
713 "foo" => count += 1,
714 "foo2" => count += 2,
715 _ => unreachable!(),
716 };
717 Ok(())
718 });
719 assert_eq!(count, 3);
720 #[cfg(target_arch = "x86_64")]
721 assert_eq!(device_manager.used_irqs_count(), 2);
722 }
723
724 #[test]
725 fn test_no_irq_allocation() {
726 let mut device_manager = MMIODeviceManager::new();
727 let mut resource_allocator = ResourceAllocator::new();
728
729 let device_info = device_manager
730 .allocate_mmio_resources(&mut resource_allocator, 0)
731 .unwrap();
732 assert!(device_info.gsi.is_none());
733 }
734
735 #[test]
736 fn test_irq_allocation() {
737 let mut device_manager = MMIODeviceManager::new();
738 let mut resource_allocator = ResourceAllocator::new();
739
740 let device_info = device_manager
741 .allocate_mmio_resources(&mut resource_allocator, 1)
742 .unwrap();
743 assert_eq!(device_info.gsi.unwrap(), crate::arch::GSI_LEGACY_START);
744 }
745
746 #[test]
747 fn test_allocation_failure() {
748 let mut device_manager = MMIODeviceManager::new();
749 let mut resource_allocator = ResourceAllocator::new();
750 assert_eq!(
751 format!(
752 "{}",
753 device_manager
754 .allocate_mmio_resources(&mut resource_allocator, 2)
755 .unwrap_err()
756 ),
757 "Invalid MMIO IRQ configuration.".to_string()
758 );
759 }
760}