vmm/device_manager/
mmio.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7
8use 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/// Errors for MMIO device manager.
35#[derive(Debug, thiserror::Error, displaydoc::Display)]
36pub enum MmioError {
37    /// Failed to allocate requested resource: {0}
38    Allocator(#[from] vm_allocator::Error),
39    /// Failed to insert device on the bus: {0}
40    BusInsert(#[from] BusError),
41    /// Failed to allocate requested resourc: {0}
42    Cmdline(#[from] linux_loader::cmdline::Error),
43    /// Could not create IRQ for MMIO device: {0}
44    CreateIrq(#[from] std::io::Error),
45    /// Invalid MMIO IRQ configuration.
46    InvalidIrqConfig,
47    /// Failed to register IO event: {0}
48    RegisterIoEvent(kvm_ioctls::Error),
49    /// Failed to register irqfd: {0}
50    RegisterIrqFd(kvm_ioctls::Error),
51    #[cfg(target_arch = "x86_64")]
52    /// Failed to create AML code for device
53    AmlError(#[from] aml::AmlError),
54}
55
56/// This represents the size of the mmio device specified to the kernel through ACPI and as a
57/// command line option.
58/// It has to be larger than 0x100 (the offset where the configuration space starts from
59/// the beginning of the memory mapped device registers) + the size of the configuration space
60/// Currently hardcoded to 4K.
61pub const MMIO_LEN: u64 = 0x1000;
62
63/// Stores the address range and irq allocated to this device.
64#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
65pub struct MMIODeviceInfo {
66    /// Mmio address at which the device is registered.
67    pub addr: u64,
68    /// Mmio addr range length.
69    pub len: u64,
70    /// Used GSI (interrupt line) for the device.
71    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)]
109/// A descriptor for MMIO devices
110pub struct MMIODevice<T> {
111    /// MMIO resources allocated to the device
112    pub(crate) resources: MMIODeviceInfo,
113    /// The actual device
114    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/// Manages the complexities of registering a MMIO device.
124#[derive(Debug, Default)]
125pub struct MMIODeviceManager {
126    /// VirtIO devices using an MMIO transport layer
127    pub(crate) virtio_devices: HashMap<(u32, String), MMIODevice<MmioTransport>>,
128    /// Boot timer device
129    pub(crate) boot_timer: Option<MMIODevice<BootTimer>>,
130    #[cfg(target_arch = "aarch64")]
131    /// Real-Time clock on Aarch64 platforms
132    pub(crate) rtc: Option<MMIODevice<RTCDevice>>,
133    #[cfg(target_arch = "aarch64")]
134    /// Serial device on Aarch64 platforms
135    pub(crate) serial: Option<MMIODevice<SerialDevice>>,
136    #[cfg(target_arch = "x86_64")]
137    // We create the AML byte code for every VirtIO device in the order we build
138    // it, so that we ensure the root block device is appears first in the DSDT.
139    // This is needed, so that the root device appears as `/dev/vda` in the guest
140    // filesystem.
141    // The alternative would be that we iterate the bus to get the data after all
142    // of the devices are build. However, iterating the bus won't give us the
143    // devices in the order they were added.
144    pub(crate) dsdt_data: Vec<u8>,
145}
146
147impl MMIODeviceManager {
148    /// Create a new DeviceManager handling mmio devices (virtio net, block).
149    pub fn new() -> MMIODeviceManager {
150        Default::default()
151    }
152
153    /// Allocates resources for a new device to be added.
154    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    /// Register a virtio-over-MMIO device to be used via MMIO transport at a specific slot.
178    pub fn register_mmio_virtio(
179        &mut self,
180        vm: &Vm,
181        device_id: String,
182        device: MMIODevice<MmioTransport>,
183    ) -> Result<(), MmioError> {
184        // Our virtio devices are currently hardcoded to use a single IRQ.
185        // Validate that requirement.
186        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    /// Append a registered virtio-over-MMIO device to the kernel cmdline.
220    #[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        // as per doc, [virtio_mmio.]device=<size>@<baseaddr>:<irq> needs to be appended
226        // to kernel command line for virtio mmio devices to get recognized
227        // the size parameter has to be transformed to KiB, so dividing hexadecimal value in
228        // bytes to 1024; further, the '{}' formatting rust construct will automatically
229        // transform it to decimal
230        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    /// Allocate slot and register an already created virtio-over-MMIO device. Also Adds the device
241    /// to the boot cmdline.
242    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                // We are sure that `irqs` has at least one element; allocate_mmio_resources makes
262                // sure of it.
263                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    /// Register an early console at the specified MMIO configuration if given as parameter,
272    /// otherwise allocate a new MMIO resources for it.
273    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        // Create a new MMIODeviceInfo object on boot path or unwrap the
280        // existing object on restore path.
281        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    /// Append the registered early console to the kernel cmdline.
315    ///
316    /// This assumes that the device has been registered with the device manager.
317    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    /// Create and register a MMIO RTC device at the specified MMIO configuration if
331    /// given as parameter, otherwise allocate a new MMIO resources for it.
332    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        // Create a new MMIODeviceInfo object on boot path or unwrap the
339        // existing object on restore path.
340        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    /// Register a boot timer device.
366    pub fn register_mmio_boot_timer(
367        &mut self,
368        mmio_bus: &Bus,
369        boot_timer: Arc<Mutex<BootTimer>>,
370    ) -> Result<(), MmioError> {
371        // Attach a new boot timer device.
372        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    /// Gets the specified device.
394    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    /// Run fn for each registered virtio device.
404    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        /// Gets the number of interrupts used by the devices registered.
476        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}