vmm/devices/acpi/
vmgenid.rs

1// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::convert::Infallible;
5
6use acpi_tables::{Aml, aml};
7use aws_lc_rs::error::Unspecified as RandError;
8use aws_lc_rs::rand;
9use log::{debug, error};
10use serde::{Deserialize, Serialize};
11use vm_memory::{GuestAddress, GuestMemoryError};
12use vm_superio::Trigger;
13use vmm_sys_util::eventfd::EventFd;
14
15use super::super::legacy::EventFdTrigger;
16use crate::snapshot::Persist;
17use crate::vstate::memory::{Bytes, GuestMemoryMmap};
18use crate::vstate::resources::ResourceAllocator;
19
20/// Bytes of memory we allocate for VMGenID device
21pub const VMGENID_MEM_SIZE: u64 = 16;
22
23/// Virtual Machine Generation ID device
24///
25/// VMGenID is an emulated device which exposes to the guest a 128-bit cryptographically random
26/// integer value which will be different every time the virtual machine executes from a different
27/// configuration file. In Firecracker terms this translates to a different value every time a new
28/// microVM is created, either from scratch or restored from a snapshot.
29///
30/// The device specification can be found here: https://go.microsoft.com/fwlink/?LinkId=260709
31#[derive(Debug)]
32pub struct VmGenId {
33    /// Current generation ID of guest VM
34    pub gen_id: u128,
35    /// Interrupt line for notifying the device about generation ID changes
36    pub interrupt_evt: EventFdTrigger,
37    /// Guest physical address where VMGenID data lives.
38    pub guest_address: GuestAddress,
39    /// GSI number for the device
40    pub gsi: u32,
41}
42
43impl VmGenId {
44    /// Create a new Vm Generation Id device using an address in the guest for writing the
45    /// generation ID and a GSI for sending device notifications.
46    pub fn from_parts(guest_address: GuestAddress, gsi: u32) -> Self {
47        debug!(
48            "vmgenid: building VMGenID device. Address: {:#010x}. IRQ: {}",
49            guest_address.0, gsi
50        );
51        let interrupt_evt = EventFdTrigger::new(
52            EventFd::new(libc::EFD_NONBLOCK)
53                .expect("vmgenid: Could not create EventFd for VMGenID device"),
54        );
55        let gen_id = Self::make_genid();
56
57        Self {
58            gen_id,
59            interrupt_evt,
60            guest_address,
61            gsi,
62        }
63    }
64
65    /// Create a new VMGenID device
66    ///
67    /// Allocate memory and a GSI for sending notifications and build the device
68    pub fn new(resource_allocator: &mut ResourceAllocator) -> Self {
69        let gsi = resource_allocator
70            .allocate_gsi_legacy(1)
71            .expect("vmgenid: Could not allocate GSI for VMGenID");
72        // The generation ID needs to live in an 8-byte aligned buffer
73        let addr = resource_allocator
74            .allocate_system_memory(VMGENID_MEM_SIZE, 8, vm_allocator::AllocPolicy::LastMatch)
75            .expect("vmgenid: Could not allocate guest RAM for VMGenID");
76
77        Self::from_parts(GuestAddress(addr), gsi[0])
78    }
79
80    // Create a 16-bytes random number
81    fn make_genid() -> u128 {
82        let mut gen_id_bytes = [0u8; 16];
83        rand::fill(&mut gen_id_bytes).expect("vmgenid: could not create new generation ID");
84        u128::from_le_bytes(gen_id_bytes)
85    }
86
87    /// Send an ACPI notification to guest device.
88    ///
89    /// This will only have effect if we have updated the generation ID in guest memory, i.e. when
90    /// re-creating the device after snapshot resumption.
91    pub fn notify_guest(&mut self) -> Result<(), std::io::Error> {
92        self.interrupt_evt
93            .trigger()
94            .inspect_err(|err| error!("vmgenid: could not send guest notification: {err}"))?;
95        debug!("vmgenid: notifying guest about new generation ID");
96        Ok(())
97    }
98
99    /// Attach the [`VmGenId`] device
100    pub fn activate(&self, mem: &GuestMemoryMmap) -> Result<(), GuestMemoryError> {
101        debug!(
102            "vmgenid: writing new generation ID to guest: {:#034x}",
103            self.gen_id
104        );
105        mem.write_slice(&self.gen_id.to_le_bytes(), self.guest_address)
106            .inspect_err(|err| error!("vmgenid: could not write generation ID to guest: {err}"))?;
107
108        Ok(())
109    }
110}
111
112/// Logic to save/restore the state of a VMGenID device
113
114#[derive(Default, Debug, Clone, Serialize, Deserialize)]
115pub struct VMGenIDState {
116    /// GSI used for VMGenID device
117    pub gsi: u32,
118    /// memory address of generation ID
119    pub addr: u64,
120}
121
122impl<'a> Persist<'a> for VmGenId {
123    type State = VMGenIDState;
124    type ConstructorArgs = ();
125    type Error = Infallible;
126
127    fn save(&self) -> Self::State {
128        VMGenIDState {
129            gsi: self.gsi,
130            addr: self.guest_address.0,
131        }
132    }
133
134    fn restore(_: Self::ConstructorArgs, state: &Self::State) -> Result<Self, Self::Error> {
135        Ok(Self::from_parts(GuestAddress(state.addr), state.gsi))
136    }
137}
138
139impl Aml for VmGenId {
140    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
141        #[allow(clippy::cast_possible_truncation)]
142        let addr_low = self.guest_address.0 as u32;
143        let addr_high = (self.guest_address.0 >> 32) as u32;
144        aml::Device::new(
145            "_SB_.VGEN".try_into()?,
146            vec![
147                &aml::Name::new("_HID".try_into()?, &"FCVMGID")?,
148                &aml::Name::new("_CID".try_into()?, &"VM_Gen_Counter")?,
149                &aml::Name::new("_DDN".try_into()?, &"VM_Gen_Counter")?,
150                &aml::Name::new(
151                    "ADDR".try_into()?,
152                    &aml::Package::new(vec![&addr_low, &addr_high]),
153                )?,
154            ],
155        )
156        .append_aml_bytes(v)
157    }
158}