vm_device/
resources.rs

1// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Structs to manage device resources.
5//!
6//! The high level flow of resource management among the VMM, the device manager, and the device
7//! is as below:
8//! 1) the VMM creates a new device object.
9//! 2) the VMM asks the new device object for its resource constraints.
10//! 3) the VMM allocates resources for the device object according to resource constraints.
11//! 4) the VMM passes the allocated resources to the device object.
12//! 5) the VMM registers the new device onto corresponding device managers according the allocated
13//!    resources.
14
15use std::{u16, u32, u64};
16
17/// Enumeration describing a device's resource constraints.
18pub enum ResourceConstraint {
19    /// Constraint for an IO Port address range.
20    PioAddress {
21        /// Allocating resource within the range [`min`, `max`] if specified.
22        range: Option<(u16, u16)>,
23        /// Alignment for the allocated address.
24        align: u16,
25        /// Size for the allocated address range.
26        size: u16,
27    },
28    /// Constraint for a Memory Mapped IO address range.
29    MmioAddress {
30        /// Allocating resource within the range [`min`, `max`] if specified.
31        range: Option<(u64, u64)>,
32        /// Alignment for the allocated address.
33        align: u64,
34        /// Size for the allocated address range.
35        size: u64,
36    },
37    /// Constraint for a legacy IRQ.
38    LegacyIrq {
39        /// Reserving the pre-allocated IRQ if it's specified.
40        irq: Option<u32>,
41    },
42    /// Constraint for PCI MSI IRQs.
43    PciMsiIrq {
44        /// Number of Irqs to allocate.
45        size: u32,
46    },
47    /// Constraint for PCI MSIx IRQs.
48    PciMsixIrq {
49        /// Number of Irqs to allocate.
50        size: u32,
51    },
52    /// Constraint for generic IRQs.
53    GenericIrq {
54        /// Number of Irqs to allocate.
55        size: u32,
56    },
57    /// Constraint for KVM mem_slot indexes to map memory into the guest.
58    KvmMemSlot {
59        /// Allocating kvm memory slots starting from the index `slot` if specified.
60        slot: Option<u32>,
61        /// Number of slots to allocate.
62        size: u32,
63    },
64}
65
66impl ResourceConstraint {
67    /// Create a new PIO address constraint object with default configuration.
68    pub fn new_pio(size: u16) -> Self {
69        ResourceConstraint::PioAddress {
70            range: None,
71            align: 0x1,
72            size,
73        }
74    }
75
76    /// Create a new PIO address constraint object.
77    pub fn pio_with_constraints(size: u16, range: Option<(u16, u16)>, align: u16) -> Self {
78        ResourceConstraint::PioAddress { range, align, size }
79    }
80
81    /// Create a new MMIO address constraint object with default configuration.
82    pub fn new_mmio(size: u64) -> Self {
83        ResourceConstraint::MmioAddress {
84            range: None,
85            align: 0x1000,
86            size,
87        }
88    }
89
90    /// Create a new MMIO address constraint object.
91    pub fn mmio_with_constraints(size: u64, range: Option<(u64, u64)>, align: u64) -> Self {
92        ResourceConstraint::MmioAddress { range, align, size }
93    }
94
95    /// Create a new legacy IRQ constraint object.
96    ///
97    /// Allocating the pre-allocated legacy Irq `irq` if specified.
98    pub fn new_legacy_irq(irq: Option<u32>) -> Self {
99        ResourceConstraint::LegacyIrq { irq }
100    }
101
102    /// Create a new KVM memory slot constraint object.
103    ///
104    /// Allocating kvm memory slots starting from the index `slot` if specified.
105    pub fn new_kvm_mem_slot(size: u32, slot: Option<u32>) -> Self {
106        ResourceConstraint::KvmMemSlot { slot, size }
107    }
108}
109
110/// Type of Message Singaled Interrupt
111#[derive(Copy, Clone, PartialEq)]
112pub enum MsiIrqType {
113    /// PCI MSI IRQ numbers.
114    PciMsi,
115    /// PCI MSIx IRQ numbers.
116    PciMsix,
117    /// Generic MSI IRQ numbers.
118    GenericMsi,
119}
120
121/// Enumeration for device resources.
122#[allow(missing_docs)]
123#[derive(Clone)]
124pub enum Resource {
125    /// IO Port address range.
126    PioAddressRange { base: u16, size: u16 },
127    /// Memory Mapped IO address range.
128    MmioAddressRange { base: u64, size: u64 },
129    /// Legacy IRQ number.
130    LegacyIrq(u32),
131    /// Message Signaled Interrupt
132    MsiIrq {
133        ty: MsiIrqType,
134        base: u32,
135        size: u32,
136    },
137    /// Network Interface Card MAC address.
138    MacAddresss(String),
139    /// KVM memslot index.
140    KvmMemSlot(u32),
141}
142
143/// Newtype to store a set of device resources.
144#[derive(Default, Clone)]
145pub struct DeviceResources(Vec<Resource>);
146
147impl DeviceResources {
148    /// Create a container object to store device resources.
149    pub fn new() -> Self {
150        DeviceResources(Vec::new())
151    }
152
153    /// Append a device resource to the container object.
154    pub fn append(&mut self, entry: Resource) {
155        self.0.push(entry);
156    }
157
158    /// Get the IO port address resources.
159    pub fn get_pio_address_ranges(&self) -> Vec<(u16, u16)> {
160        let mut vec = Vec::new();
161        for entry in self.0.iter().as_ref() {
162            if let Resource::PioAddressRange { base, size } = entry {
163                vec.push((*base, *size));
164            }
165        }
166        vec
167    }
168
169    /// Get the Memory Mapped IO address resources.
170    pub fn get_mmio_address_ranges(&self) -> Vec<(u64, u64)> {
171        let mut vec = Vec::new();
172        for entry in self.0.iter().as_ref() {
173            if let Resource::MmioAddressRange { base, size } = entry {
174                vec.push((*base, *size));
175            }
176        }
177        vec
178    }
179
180    /// Get the first legacy interrupt number(IRQ).
181    pub fn get_legacy_irq(&self) -> Option<u32> {
182        for entry in self.0.iter().as_ref() {
183            if let Resource::LegacyIrq(base) = entry {
184                return Some(*base);
185            }
186        }
187        None
188    }
189
190    /// Get information about the first PCI MSI interrupt resource.
191    pub fn get_pci_msi_irqs(&self) -> Option<(u32, u32)> {
192        self.get_msi_irqs(MsiIrqType::PciMsi)
193    }
194
195    /// Get information about the first PCI MSIx interrupt resource.
196    pub fn get_pci_msix_irqs(&self) -> Option<(u32, u32)> {
197        self.get_msi_irqs(MsiIrqType::PciMsix)
198    }
199
200    /// Get information about the first Generic MSI interrupt resource.
201    pub fn get_generic_msi_irqs(&self) -> Option<(u32, u32)> {
202        self.get_msi_irqs(MsiIrqType::GenericMsi)
203    }
204
205    fn get_msi_irqs(&self, ty: MsiIrqType) -> Option<(u32, u32)> {
206        for entry in self.0.iter().as_ref() {
207            if let Resource::MsiIrq {
208                ty: msi_type,
209                base,
210                size,
211            } = entry
212            {
213                if ty == *msi_type {
214                    return Some((*base, *size));
215                }
216            }
217        }
218        None
219    }
220
221    /// Get the KVM memory slots to map memory into the guest.
222    pub fn get_kvm_mem_slots(&self) -> Vec<u32> {
223        let mut vec = Vec::new();
224        for entry in self.0.iter().as_ref() {
225            if let Resource::KvmMemSlot(index) = entry {
226                vec.push(*index);
227            }
228        }
229        vec
230    }
231
232    /// Get the first resource information for NIC MAC address.
233    pub fn get_mac_address(&self) -> Option<String> {
234        for entry in self.0.iter().as_ref() {
235            if let Resource::MacAddresss(addr) = entry {
236                return Some(addr.clone());
237            }
238        }
239        None
240    }
241
242    /// Get immutable reference to all the resources.
243    pub fn get_all_resources(&self) -> &[Resource] {
244        &self.0
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    const PIO_ADDRESS_SIZE: u16 = 5;
253    const PIO_ADDRESS_BASE: u16 = 0;
254    const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321;
255    const MMIO_ADDRESS_BASE: u64 = 0x1234_5678;
256    const LEGACY_IRQ: u32 = 0x168;
257    const PCI_MSI_IRQ_SIZE: u32 = 0x8888;
258    const PCI_MSI_IRQ_BASE: u32 = 0x6666;
259    const PCI_MSIX_IRQ_SIZE: u32 = 0x16666;
260    const PCI_MSIX_IRQ_BASE: u32 = 0x8888;
261    const GENERIC_MSI_IRQS_SIZE: u32 = 0x16888;
262    const GENERIC_MSI_IRQS_BASE: u32 = 0x16688;
263    const MAC_ADDRESS: &str = "00:08:63:66:86:88";
264    const KVM_SLOT_ID: u32 = 0x0100;
265
266    fn get_device_resource() -> DeviceResources {
267        let entry = Resource::PioAddressRange {
268            base: PIO_ADDRESS_BASE,
269            size: PIO_ADDRESS_SIZE,
270        };
271        let mut resource = DeviceResources::new();
272        resource.append(entry);
273        let entry = Resource::MmioAddressRange {
274            base: MMIO_ADDRESS_BASE,
275            size: MMIO_ADDRESS_SIZE,
276        };
277        resource.append(entry);
278        let entry = Resource::LegacyIrq(LEGACY_IRQ);
279        resource.append(entry);
280        let entry = Resource::MsiIrq {
281            ty: MsiIrqType::PciMsi,
282            base: PCI_MSI_IRQ_BASE,
283            size: PCI_MSI_IRQ_SIZE,
284        };
285        resource.append(entry);
286        let entry = Resource::MsiIrq {
287            ty: MsiIrqType::PciMsix,
288            base: PCI_MSIX_IRQ_BASE,
289            size: PCI_MSIX_IRQ_SIZE,
290        };
291        resource.append(entry);
292        let entry = Resource::MsiIrq {
293            ty: MsiIrqType::GenericMsi,
294            base: GENERIC_MSI_IRQS_BASE,
295            size: GENERIC_MSI_IRQS_SIZE,
296        };
297        resource.append(entry);
298        let entry = Resource::MacAddresss(MAC_ADDRESS.to_string());
299        resource.append(entry);
300
301        resource.append(Resource::KvmMemSlot(KVM_SLOT_ID));
302
303        resource
304    }
305
306    #[test]
307    fn get_pio_address_ranges() {
308        let resources = get_device_resource();
309        assert!(
310            resources.get_pio_address_ranges()[0].0 == PIO_ADDRESS_BASE
311                && resources.get_pio_address_ranges()[0].1 == PIO_ADDRESS_SIZE
312        );
313    }
314
315    #[test]
316    fn test_get_mmio_address_ranges() {
317        let resources = get_device_resource();
318        assert!(
319            resources.get_mmio_address_ranges()[0].0 == MMIO_ADDRESS_BASE
320                && resources.get_mmio_address_ranges()[0].1 == MMIO_ADDRESS_SIZE
321        );
322    }
323
324    #[test]
325    fn test_get_legacy_irq() {
326        let resources = get_device_resource();
327        assert!(resources.get_legacy_irq().unwrap() == LEGACY_IRQ);
328    }
329
330    #[test]
331    fn test_get_pci_msi_irqs() {
332        let resources = get_device_resource();
333        assert!(
334            resources.get_pci_msi_irqs().unwrap().0 == PCI_MSI_IRQ_BASE
335                && resources.get_pci_msi_irqs().unwrap().1 == PCI_MSI_IRQ_SIZE
336        );
337    }
338
339    #[test]
340    fn test_pci_msix_irqs() {
341        let resources = get_device_resource();
342        assert!(
343            resources.get_pci_msix_irqs().unwrap().0 == PCI_MSIX_IRQ_BASE
344                && resources.get_pci_msix_irqs().unwrap().1 == PCI_MSIX_IRQ_SIZE
345        );
346    }
347
348    #[test]
349    fn test_get_generic_msi_irqs() {
350        let resources = get_device_resource();
351        assert!(
352            resources.get_generic_msi_irqs().unwrap().0 == GENERIC_MSI_IRQS_BASE
353                && resources.get_generic_msi_irqs().unwrap().1 == GENERIC_MSI_IRQS_SIZE
354        );
355    }
356
357    #[test]
358    fn test_get_mac_address() {
359        let resources = get_device_resource();
360        assert_eq!(resources.get_mac_address().unwrap(), MAC_ADDRESS);
361    }
362
363    #[test]
364    fn test_get_kvm_slot() {
365        let resources = get_device_resource();
366        assert_eq!(resources.get_kvm_mem_slots(), vec![KVM_SLOT_ID]);
367    }
368
369    #[test]
370    fn test_get_all_resources() {
371        let resources = get_device_resource();
372        assert_eq!(resources.get_all_resources().len(), 8);
373    }
374
375    #[test]
376    fn test_resource_constraint() {
377        if let ResourceConstraint::PioAddress { range, align, size } =
378            ResourceConstraint::new_pio(2)
379        {
380            assert_eq!(range, None);
381            assert_eq!(align, 1);
382            assert_eq!(size, 2);
383        } else {
384            panic!("Pio resource constraint is invalid.");
385        }
386
387        if let ResourceConstraint::PioAddress { range, align, size } =
388            ResourceConstraint::pio_with_constraints(2, Some((15, 16)), 2)
389        {
390            assert_eq!(range, Some((15, 16)));
391            assert_eq!(align, 2);
392            assert_eq!(size, 2);
393        } else {
394            panic!("Pio resource constraint is invalid.");
395        }
396
397        if let ResourceConstraint::MmioAddress { range, align, size } =
398            ResourceConstraint::new_mmio(0x2000)
399        {
400            assert_eq!(range, None);
401            assert_eq!(align, 0x1000);
402            assert_eq!(size, 0x2000);
403        } else {
404            panic!("Mmio resource constraint is invalid.");
405        }
406
407        if let ResourceConstraint::MmioAddress { range, align, size } =
408            ResourceConstraint::mmio_with_constraints(0x2000, Some((0x0, 0x2000)), 0x2000)
409        {
410            assert_eq!(range, Some((0x0, 0x2000)));
411            assert_eq!(align, 0x2000);
412            assert_eq!(size, 0x2000);
413        } else {
414            panic!("Mmio resource constraint is invalid.");
415        }
416
417        if let ResourceConstraint::LegacyIrq { irq } =
418            ResourceConstraint::new_legacy_irq(Some(0x123))
419        {
420            assert_eq!(irq, Some(0x123));
421        } else {
422            panic!("IRQ resource constraint is invalid.");
423        }
424
425        if let ResourceConstraint::KvmMemSlot { slot, size } =
426            ResourceConstraint::new_kvm_mem_slot(0x1000, Some(0x2000))
427        {
428            assert_eq!(slot, Some(0x2000));
429            assert_eq!(size, 0x1000);
430        } else {
431            panic!("KVM slot resource constraint is invalid.");
432        }
433    }
434}