dbs_device/
device_manager.rs

1// Copyright 2020-2022 Alibaba Cloud. All Rights Reserved.
2// Copyright © 2019 Intel Corporation. All Rights Reserved.
3// SPDX-License-Identifier: Apache-2.0
4
5//! IO Device Manager to handle trapped MMIO/PIO access requests.
6//!
7//! The [IoManager](self::IoManager) is responsible for managing all trapped MMIO/PIO accesses for
8//! virtual devices. It cooperates with the Secure Sandbox/VMM and device drivers to handle trapped
9//! accesses. The flow is as below:
10//! - device drivers allocate resources from the VMM/resource manager, including trapped MMIO/PIO
11//!   address ranges.
12//! - the device manager registers devices to the [IoManager](self::IoManager) with trapped MMIO/PIO
13//!   address ranges.
14//! - VM IO Exit events get triggered when the guest accesses those trapped address ranges.
15//! - the vmm handle those VM IO Exit events, and dispatch them to the [IoManager].
16//! - the [IoManager] invokes registered callbacks/device drivers to handle those accesses, if there
17//!   is a device registered for the address.
18//!
19//! # Examples
20//!
21//! Creating a dummy deivce which implement DeviceIo trait, and register it to [IoManager] with
22//! trapped MMIO/PIO address ranges:
23//!
24//! ```
25//! use std::sync::Arc;
26//! use std::any::Any;
27//!
28//! use dbs_device::device_manager::IoManager;
29//! use dbs_device::resources::{DeviceResources, Resource};
30//! use dbs_device::{DeviceIo, IoAddress, PioAddress};
31//!
32//! struct DummyDevice {}
33//!
34//! impl DeviceIo for DummyDevice {
35//!     fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]) {
36//!         println!(
37//!             "mmio read, base: 0x{:x}, offset: 0x{:x}",
38//!             base.raw_value(),
39//!             offset.raw_value()
40//!         );
41//!     }
42//!
43//!     fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]) {
44//!         println!(
45//!             "mmio write, base: 0x{:x}, offset: 0x{:x}",
46//!             base.raw_value(),
47//!             offset.raw_value()
48//!         );
49//!     }
50//!
51//!     fn pio_read(&self, base: PioAddress, offset: PioAddress, data: &mut [u8]) {
52//!         println!(
53//!             "pio read, base: 0x{:x}, offset: 0x{:x}",
54//!             base.raw_value(),
55//!             offset.raw_value()
56//!         );
57//!     }
58//!
59//!     fn pio_write(&self, base: PioAddress, offset: PioAddress, data: &[u8]) {
60//!         println!(
61//!             "pio write, base: 0x{:x}, offset: 0x{:x}",
62//!             base.raw_value(),
63//!             offset.raw_value()
64//!         );
65//!     }
66//!
67//!     fn as_any(&self) -> &dyn Any {
68//!         self
69//!     }
70//! }
71//!
72//! // Allocate resources for device
73//! let mut resources = DeviceResources::new();
74//! resources.append(Resource::MmioAddressRange {
75//!     base: 0,
76//!     size: 4096,
77//! });
78//! resources.append(Resource::PioAddressRange { base: 0, size: 32 });
79//!
80//! // Register device to `IoManager` with resources
81//! let device = Arc::new(DummyDevice {});
82//! let mut manager = IoManager::new();
83//! manager.register_device_io(device, &resources).unwrap();
84//!
85//! // Dispatch I/O event from `IoManager` to device
86//! manager.mmio_write(0, &vec![0, 1]).unwrap();
87//! {
88//!     let mut buffer = vec![0; 4];
89//!     manager.pio_read(0, &mut buffer);
90//! }
91//! ```
92
93use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
94use std::collections::btree_map::BTreeMap;
95use std::ops::Deref;
96use std::result;
97use std::sync::Arc;
98
99use thiserror::Error;
100
101use crate::resources::Resource;
102use crate::{DeviceIo, IoAddress, IoSize, PioAddress};
103
104/// Error types for `IoManager` related operations.
105#[derive(Error, Debug)]
106pub enum Error {
107    /// The inserting device overlaps with a current device.
108    #[error("device address conflicts with existing devices")]
109    DeviceOverlap,
110    /// The device doesn't exist.
111    #[error("no such device")]
112    NoDevice,
113}
114
115/// A specialized version of [std::result::Result] for [IoManager] realted operations.
116pub type Result<T> = result::Result<T, Error>;
117
118/// Structure representing an IO address range.
119#[derive(Debug, Copy, Clone, Eq)]
120pub struct IoRange {
121    base: IoAddress,
122    size: IoSize,
123}
124
125impl IoRange {
126    fn new_pio_range(base: u16, size: u16) -> Self {
127        IoRange {
128            base: IoAddress(base as u64),
129            size: IoSize(size as u64),
130        }
131    }
132
133    fn new_mmio_range(base: u64, size: u64) -> Self {
134        IoRange {
135            base: IoAddress(base),
136            size: IoSize(size),
137        }
138    }
139}
140
141impl PartialEq for IoRange {
142    fn eq(&self, other: &IoRange) -> bool {
143        self.base == other.base
144    }
145}
146
147impl Ord for IoRange {
148    fn cmp(&self, other: &IoRange) -> Ordering {
149        self.base.cmp(&other.base)
150    }
151}
152
153impl PartialOrd for IoRange {
154    fn partial_cmp(&self, other: &IoRange) -> Option<Ordering> {
155        self.base.partial_cmp(&other.base)
156    }
157}
158
159/// IO manager to handle all trapped MMIO/PIO access requests.
160///
161/// All devices handling trapped MMIO/PIO accesses should register themself to the IO manager
162/// with trapped address ranges. When guest vm accesses those trapped MMIO/PIO address ranges,
163/// VM IO Exit events will be triggered and the VMM dispatches those events to IO manager.
164/// And then the registered callbacks will invoked by IO manager.
165#[derive(Clone, Default)]
166pub struct IoManager {
167    /// Range mapping for VM exit pio operations.
168    pio_bus: BTreeMap<IoRange, Arc<dyn DeviceIo>>,
169    /// Range mapping for VM exit mmio operations.
170    mmio_bus: BTreeMap<IoRange, Arc<dyn DeviceIo>>,
171}
172
173impl IoManager {
174    /// Create a new instance of [IoManager].
175    pub fn new() -> Self {
176        IoManager::default()
177    }
178
179    /// Register a new device to the [IoManager], with trapped MMIO/PIO address ranges.
180    ///
181    /// # Arguments
182    ///
183    /// * `device`: device object to handle trapped IO access requests
184    /// * `resources`: resources representing trapped MMIO/PIO address ranges. Only MMIO/PIO address
185    ///    ranges will be handled, and other types of resource will be ignored. So the caller does
186    ///    not need to filter out non-MMIO/PIO resources.
187    pub fn register_device_io(
188        &mut self,
189        device: Arc<dyn DeviceIo>,
190        resources: &[Resource],
191    ) -> Result<()> {
192        for (idx, res) in resources.iter().enumerate() {
193            match *res {
194                Resource::PioAddressRange { base, size } => {
195                    if self
196                        .pio_bus
197                        .insert(IoRange::new_pio_range(base, size), device.clone())
198                        .is_some()
199                    {
200                        // Rollback registered resources.
201                        self.unregister_device_io(&resources[0..idx])
202                            .expect("failed to unregister devices");
203
204                        return Err(Error::DeviceOverlap);
205                    }
206                }
207                Resource::MmioAddressRange { base, size } => {
208                    if self
209                        .mmio_bus
210                        .insert(IoRange::new_mmio_range(base, size), device.clone())
211                        .is_some()
212                    {
213                        // Rollback registered resources.
214                        self.unregister_device_io(&resources[0..idx])
215                            .expect("failed to unregister devices");
216
217                        return Err(Error::DeviceOverlap);
218                    }
219                }
220                _ => continue,
221            }
222        }
223        Ok(())
224    }
225
226    /// Unregister a device from `IoManager`.
227    ///
228    /// # Arguments
229    ///
230    /// * `resources`: resource list containing all trapped address ranges for the device.
231    pub fn unregister_device_io(&mut self, resources: &[Resource]) -> Result<()> {
232        for res in resources.iter() {
233            match *res {
234                Resource::PioAddressRange { base, size } => {
235                    self.pio_bus.remove(&IoRange::new_pio_range(base, size));
236                }
237                Resource::MmioAddressRange { base, size } => {
238                    self.mmio_bus.remove(&IoRange::new_mmio_range(base, size));
239                }
240                _ => continue,
241            }
242        }
243        Ok(())
244    }
245
246    /// Handle VM IO Exit events triggered by trapped MMIO read accesses.
247    ///
248    /// Return error if failed to get the device.
249    pub fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Result<()> {
250        self.get_mmio_device(IoAddress(addr))
251            .map(|(device, base)| device.read(base, IoAddress(addr - base.raw_value()), data))
252            .ok_or(Error::NoDevice)
253    }
254
255    /// Handle VM IO Exit events triggered by trapped MMIO write accesses.
256    ///
257    /// Return error if failed to get the device.
258    pub fn mmio_write(&self, addr: u64, data: &[u8]) -> Result<()> {
259        self.get_mmio_device(IoAddress(addr))
260            .map(|(device, base)| device.write(base, IoAddress(addr - base.raw_value()), data))
261            .ok_or(Error::NoDevice)
262    }
263
264    /// Get the registered device handling the trapped MMIO address `addr`.
265    fn get_mmio_device(&self, addr: IoAddress) -> Option<(&Arc<dyn DeviceIo>, IoAddress)> {
266        let range = IoRange::new_mmio_range(addr.raw_value(), 0);
267        if let Some((range, dev)) = self.mmio_bus.range(..=&range).nth_back(0) {
268            if (addr.raw_value() - range.base.raw_value()) < range.size.raw_value() {
269                return Some((dev, range.base));
270            }
271        }
272        None
273    }
274}
275
276impl IoManager {
277    /// Handle VM IO Exit events triggered by trapped PIO read accesses.
278    ///
279    /// Return error if failed to get the device.
280    pub fn pio_read(&self, addr: u16, data: &mut [u8]) -> Result<()> {
281        self.get_pio_device(PioAddress(addr))
282            .map(|(device, base)| device.pio_read(base, PioAddress(addr - base.raw_value()), data))
283            .ok_or(Error::NoDevice)
284    }
285
286    /// Handle VM IO Exit events triggered by trapped PIO write accesses.
287    ///
288    /// Return error if failed to get the device.
289    pub fn pio_write(&self, addr: u16, data: &[u8]) -> Result<()> {
290        self.get_pio_device(PioAddress(addr))
291            .map(|(device, base)| device.pio_write(base, PioAddress(addr - base.raw_value()), data))
292            .ok_or(Error::NoDevice)
293    }
294
295    /// Get the registered device handling the trapped PIO address `addr`.
296    fn get_pio_device(&self, addr: PioAddress) -> Option<(&Arc<dyn DeviceIo>, PioAddress)> {
297        let range = IoRange::new_pio_range(addr.raw_value(), 0);
298        if let Some((range, dev)) = self.pio_bus.range(..=&range).nth_back(0) {
299            if (addr.raw_value() as u64 - range.base.raw_value()) < range.size.raw_value() {
300                return Some((dev, PioAddress(range.base.0 as u16)));
301            }
302        }
303        None
304    }
305}
306
307impl PartialEq for IoManager {
308    fn eq(&self, other: &IoManager) -> bool {
309        if self.pio_bus.len() != other.pio_bus.len() {
310            return false;
311        }
312        if self.mmio_bus.len() != other.mmio_bus.len() {
313            return false;
314        }
315
316        for (io_range, device_io) in self.pio_bus.iter() {
317            if !other.pio_bus.contains_key(io_range) {
318                return false;
319            }
320            let other_device_io = &other.pio_bus[io_range];
321            if device_io.get_trapped_io_resources() != other_device_io.get_trapped_io_resources() {
322                return false;
323            }
324        }
325
326        for (io_range, device_io) in self.mmio_bus.iter() {
327            if !other.mmio_bus.contains_key(io_range) {
328                return false;
329            }
330            let other_device_io = &other.mmio_bus[io_range];
331            if device_io.get_trapped_io_resources() != other_device_io.get_trapped_io_resources() {
332                return false;
333            }
334        }
335
336        true
337    }
338}
339
340/// Trait for IO manager context object to support device hotplug at runtime.
341///
342/// The `IoManagerContext` objects are passed to devices by the IO manager, so the devices could
343/// use it to hot-add/hot-remove other devices at runtime. It provides a transaction mechanism
344/// to hot-add/hot-remove devices.
345pub trait IoManagerContext {
346    /// Type of context object passed to the callbacks.
347    type Context;
348
349    /// Begin a transaction and return a context object.
350    ///
351    /// The returned context object must be passed to commit_tx() or cancel_tx() later.
352    fn begin_tx(&self) -> Self::Context;
353
354    /// Commit the transaction.
355    fn commit_tx(&self, ctx: Self::Context);
356
357    /// Cancel the transaction.
358    fn cancel_tx(&self, ctx: Self::Context);
359
360    /// Register a new device with its associated resources to the IO manager.
361    ///
362    /// # Arguments
363    ///
364    /// * `ctx`: context object returned by begin_tx().
365    /// * `device`: device instance object to be registered
366    /// * `resources`: resources representing trapped MMIO/PIO address ranges. Only MMIO/PIO address
367    ///    ranges will be handled, and other types of resource will be ignored. So the caller does
368    ///    not need to filter out non-MMIO/PIO resources.
369    fn register_device_io(
370        &self,
371        ctx: &mut Self::Context,
372        device: Arc<dyn DeviceIo>,
373        resources: &[Resource],
374    ) -> Result<()>;
375
376    /// Unregister a device from the IO manager.
377    ///
378    /// # Arguments
379    ///
380    /// * `ctx`: context object returned by begin_tx().
381    /// * `resources`: resource list containing all trapped address ranges for the device.
382    fn unregister_device_io(&self, ctx: &mut Self::Context, resources: &[Resource]) -> Result<()>;
383}
384
385impl<T: IoManagerContext> IoManagerContext for Arc<T> {
386    type Context = T::Context;
387
388    fn begin_tx(&self) -> Self::Context {
389        self.deref().begin_tx()
390    }
391
392    fn commit_tx(&self, ctx: Self::Context) {
393        self.deref().commit_tx(ctx)
394    }
395
396    fn cancel_tx(&self, ctx: Self::Context) {
397        self.deref().cancel_tx(ctx)
398    }
399
400    fn register_device_io(
401        &self,
402        ctx: &mut Self::Context,
403        device: Arc<dyn DeviceIo>,
404        resources: &[Resource],
405    ) -> std::result::Result<(), Error> {
406        self.deref().register_device_io(ctx, device, resources)
407    }
408
409    fn unregister_device_io(
410        &self,
411        ctx: &mut Self::Context,
412        resources: &[Resource],
413    ) -> std::result::Result<(), Error> {
414        self.deref().unregister_device_io(ctx, resources)
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use std::error::Error;
421    use std::sync::Mutex;
422
423    use super::*;
424    use crate::resources::DeviceResources;
425
426    const PIO_ADDRESS_SIZE: u16 = 4;
427    const PIO_ADDRESS_BASE: u16 = 0x40;
428    const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321;
429    const MMIO_ADDRESS_BASE: u64 = 0x1234_5678;
430    const LEGACY_IRQ: u32 = 4;
431    const CONFIG_DATA: u32 = 0x1234;
432
433    struct DummyDevice {
434        config: Mutex<u32>,
435    }
436
437    impl DummyDevice {
438        fn new(config: u32) -> Self {
439            DummyDevice {
440                config: Mutex::new(config),
441            }
442        }
443    }
444
445    impl DeviceIo for DummyDevice {
446        fn read(&self, _base: IoAddress, _offset: IoAddress, data: &mut [u8]) {
447            if data.len() > 4 {
448                return;
449            }
450            for (idx, iter) in data.iter_mut().enumerate() {
451                let config = self.config.lock().expect("failed to acquire lock");
452                *iter = (*config >> (idx * 8) & 0xff) as u8;
453            }
454        }
455
456        fn write(&self, _base: IoAddress, _offset: IoAddress, data: &[u8]) {
457            let mut config = self.config.lock().expect("failed to acquire lock");
458            *config = u32::from(data[0]) & 0xff;
459        }
460
461        fn pio_read(&self, _base: PioAddress, _offset: PioAddress, data: &mut [u8]) {
462            if data.len() > 4 {
463                return;
464            }
465            for (idx, iter) in data.iter_mut().enumerate() {
466                let config = self.config.lock().expect("failed to acquire lock");
467                *iter = (*config >> (idx * 8) & 0xff) as u8;
468            }
469        }
470
471        fn pio_write(&self, _base: PioAddress, _offset: PioAddress, data: &[u8]) {
472            let mut config = self.config.lock().expect("failed to acquire lock");
473            *config = u32::from(data[0]) & 0xff;
474        }
475        fn as_any(&self) -> &dyn std::any::Any {
476            self
477        }
478    }
479
480    #[test]
481    fn test_clone_io_manager() {
482        let mut io_mgr = IoManager::new();
483        let dummy = DummyDevice::new(0);
484        let dum = Arc::new(dummy);
485
486        let mut resource: Vec<Resource> = Vec::new();
487        let mmio = Resource::MmioAddressRange {
488            base: MMIO_ADDRESS_BASE,
489            size: MMIO_ADDRESS_SIZE,
490        };
491        let irq = Resource::LegacyIrq(LEGACY_IRQ);
492
493        resource.push(mmio);
494        resource.push(irq);
495
496        let pio = Resource::PioAddressRange {
497            base: PIO_ADDRESS_BASE,
498            size: PIO_ADDRESS_SIZE,
499        };
500        resource.push(pio);
501
502        assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());
503
504        let io_mgr2 = io_mgr.clone();
505        assert_eq!(io_mgr2.mmio_bus.len(), 1);
506
507        assert_eq!(io_mgr2.pio_bus.len(), 1);
508
509        let (dev, addr) = io_mgr2
510            .get_mmio_device(IoAddress(MMIO_ADDRESS_BASE + 1))
511            .unwrap();
512        assert_eq!(Arc::strong_count(dev), 5);
513
514        assert_eq!(addr, IoAddress(MMIO_ADDRESS_BASE));
515
516        drop(io_mgr);
517        assert_eq!(Arc::strong_count(dev), 3);
518
519        drop(io_mgr2);
520        assert_eq!(Arc::strong_count(&dum), 1);
521    }
522
523    #[test]
524    fn test_register_unregister_device_io() {
525        let mut io_mgr = IoManager::new();
526        let dummy = DummyDevice::new(0);
527        let dum = Arc::new(dummy);
528
529        let mut resources = DeviceResources::new();
530        let mmio = Resource::MmioAddressRange {
531            base: MMIO_ADDRESS_BASE,
532            size: MMIO_ADDRESS_SIZE,
533        };
534        let pio = Resource::PioAddressRange {
535            base: PIO_ADDRESS_BASE,
536            size: PIO_ADDRESS_SIZE,
537        };
538        let irq = Resource::LegacyIrq(LEGACY_IRQ);
539
540        resources.append(mmio);
541        resources.append(pio);
542        resources.append(irq);
543
544        assert!(io_mgr.register_device_io(dum.clone(), &resources).is_ok());
545        assert!(io_mgr.register_device_io(dum, &resources).is_err());
546        assert!(io_mgr.unregister_device_io(&resources).is_ok())
547    }
548
549    #[test]
550    fn test_mmio_read_write() {
551        let mut io_mgr: IoManager = Default::default();
552        let dum = Arc::new(DummyDevice::new(CONFIG_DATA));
553        let mut resource: Vec<Resource> = Vec::new();
554
555        let mmio = Resource::MmioAddressRange {
556            base: MMIO_ADDRESS_BASE,
557            size: MMIO_ADDRESS_SIZE,
558        };
559        resource.push(mmio);
560        assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());
561
562        let mut data = [0; 4];
563        assert!(io_mgr.mmio_read(MMIO_ADDRESS_BASE, &mut data).is_ok());
564        assert_eq!(data, [0x34, 0x12, 0, 0]);
565
566        assert!(io_mgr
567            .mmio_read(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE, &mut data)
568            .is_err());
569
570        data = [0; 4];
571        assert!(io_mgr.mmio_write(MMIO_ADDRESS_BASE, &data).is_ok());
572        assert_eq!(*dum.config.lock().unwrap(), 0);
573
574        assert!(io_mgr
575            .mmio_write(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE, &data)
576            .is_err());
577    }
578
579    #[test]
580    fn test_pio_read_write() {
581        let mut io_mgr: IoManager = Default::default();
582        let dum = Arc::new(DummyDevice::new(CONFIG_DATA));
583        let mut resource: Vec<Resource> = Vec::new();
584
585        let pio = Resource::PioAddressRange {
586            base: PIO_ADDRESS_BASE,
587            size: PIO_ADDRESS_SIZE,
588        };
589        resource.push(pio);
590        assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());
591
592        let mut data = [0; 4];
593        assert!(io_mgr.pio_read(PIO_ADDRESS_BASE, &mut data).is_ok());
594        assert_eq!(data, [0x34, 0x12, 0, 0]);
595
596        assert!(io_mgr
597            .pio_read(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &mut data)
598            .is_err());
599
600        data = [0; 4];
601        assert!(io_mgr.pio_write(PIO_ADDRESS_BASE, &data).is_ok());
602        assert_eq!(*dum.config.lock().unwrap(), 0);
603
604        assert!(io_mgr
605            .pio_write(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &data)
606            .is_err());
607    }
608
609    #[test]
610    fn test_device_manager_data_structs() {
611        let range1 = IoRange::new_mmio_range(0x1000, 0x1000);
612        let range2 = IoRange::new_mmio_range(0x1000, 0x2000);
613        let range3 = IoRange::new_mmio_range(0x2000, 0x1000);
614
615        assert_eq!(range1, range1.clone());
616        assert_eq!(range1, range2);
617        assert!(range1 < range3);
618    }
619
620    #[test]
621    fn test_error_code() {
622        let err = super::Error::DeviceOverlap;
623
624        assert!(err.source().is_none());
625        assert_eq!(
626            format!("{}", err),
627            "device address conflicts with existing devices"
628        );
629
630        let err = super::Error::NoDevice;
631        assert!(err.source().is_none());
632        assert_eq!(format!("{:#?}", err), "NoDevice");
633    }
634
635    #[test]
636    fn test_io_manager_partial_eq() {
637        let mut io_mgr1 = IoManager::new();
638        let mut io_mgr2 = IoManager::new();
639        let dummy1 = Arc::new(DummyDevice::new(0));
640        let dummy2 = Arc::new(DummyDevice::new(0));
641
642        let mut resources1 = DeviceResources::new();
643        let mut resources2 = DeviceResources::new();
644
645        let mmio = Resource::MmioAddressRange {
646            base: MMIO_ADDRESS_BASE,
647            size: MMIO_ADDRESS_SIZE,
648        };
649        let pio = Resource::PioAddressRange {
650            base: PIO_ADDRESS_BASE,
651            size: PIO_ADDRESS_SIZE,
652        };
653
654        resources1.append(mmio.clone());
655        resources1.append(pio.clone());
656
657        resources2.append(mmio);
658        resources2.append(pio);
659
660        io_mgr1.register_device_io(dummy1, &resources1).unwrap();
661        io_mgr2.register_device_io(dummy2, &resources2).unwrap();
662
663        assert!(io_mgr1 == io_mgr2);
664    }
665
666    #[test]
667    fn test_io_manager_partial_neq() {
668        let mut io_mgr1 = IoManager::new();
669        let mut io_mgr2 = IoManager::new();
670        let dummy1 = Arc::new(DummyDevice::new(0));
671        let dummy2 = Arc::new(DummyDevice::new(0));
672
673        let mut resources1 = DeviceResources::new();
674        let mut resources2 = DeviceResources::new();
675
676        let mmio = Resource::MmioAddressRange {
677            base: MMIO_ADDRESS_BASE,
678            size: MMIO_ADDRESS_SIZE,
679        };
680        let pio = Resource::PioAddressRange {
681            base: PIO_ADDRESS_BASE,
682            size: PIO_ADDRESS_SIZE,
683        };
684
685        resources1.append(mmio.clone());
686        resources1.append(pio);
687
688        resources2.append(mmio);
689
690        io_mgr1.register_device_io(dummy1, &resources1).unwrap();
691        io_mgr2.register_device_io(dummy2, &resources2).unwrap();
692
693        assert!(io_mgr1 != io_mgr2);
694    }
695}