vmm/devices/acpi/
vmclock.rs

1// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::convert::Infallible;
5use std::mem::offset_of;
6use std::sync::atomic::{Ordering, fence};
7
8use acpi_tables::{Aml, aml};
9use log::error;
10use serde::{Deserialize, Serialize};
11use vm_allocator::AllocPolicy;
12use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryError};
13
14use crate::devices::acpi::generated::vmclock_abi::{
15    VMCLOCK_COUNTER_INVALID, VMCLOCK_MAGIC, VMCLOCK_STATUS_UNKNOWN, vmclock_abi,
16};
17use crate::snapshot::Persist;
18use crate::vstate::memory::GuestMemoryMmap;
19use crate::vstate::resources::ResourceAllocator;
20
21// SAFETY: `vmclock_abi` is a POD
22unsafe impl ByteValued for vmclock_abi {}
23
24// We are reserving a physical page to expose the [`VmClock`] data
25const VMCLOCK_SIZE: u32 = 0x1000;
26
27// Write a value in `vmclock_abi` both in the Firecracker-managed state
28// and inside guest memory address that corresponds to it.
29macro_rules! write_vmclock_field {
30    ($vmclock:expr, $mem:expr, $field:ident, $value:expr) => {
31        $vmclock.inner.$field = $value;
32        $mem.write_obj(
33            $vmclock.inner.$field,
34            $vmclock
35                .guest_address
36                .unchecked_add(offset_of!(vmclock_abi, $field) as u64),
37        );
38    };
39}
40
41/// VMclock device
42///
43/// This device emulates the VMclock device which allows passing information to the guest related
44/// to the relation of the host CPU to real-time clock as well as information about disruptive
45/// events, such as live-migration.
46#[derive(Debug)]
47pub struct VmClock {
48    /// Guest address in which we will write the VMclock struct
49    pub guest_address: GuestAddress,
50    /// The [`VmClock`] state we are exposing to the guest
51    inner: vmclock_abi,
52}
53
54impl VmClock {
55    /// Create a new [`VmClock`] device for a newly booted VM
56    pub fn new(resource_allocator: &mut ResourceAllocator) -> VmClock {
57        let addr = resource_allocator
58            .allocate_system_memory(
59                VMCLOCK_SIZE as u64,
60                VMCLOCK_SIZE as u64,
61                AllocPolicy::LastMatch,
62            )
63            .expect("vmclock: could not allocate guest memory for device");
64
65        let mut inner = vmclock_abi {
66            magic: VMCLOCK_MAGIC,
67            size: VMCLOCK_SIZE,
68            version: 1,
69            clock_status: VMCLOCK_STATUS_UNKNOWN,
70            counter_id: VMCLOCK_COUNTER_INVALID,
71            ..Default::default()
72        };
73
74        VmClock {
75            guest_address: GuestAddress(addr),
76            inner,
77        }
78    }
79
80    /// Activate [`VmClock`] device
81    pub fn activate(&self, mem: &GuestMemoryMmap) -> Result<(), GuestMemoryError> {
82        mem.write_slice(self.inner.as_slice(), self.guest_address)?;
83        Ok(())
84    }
85
86    /// Bump the VM generation counter
87    pub fn post_load_update(&mut self, mem: &GuestMemoryMmap) {
88        write_vmclock_field!(self, mem, seq_count, self.inner.seq_count | 1);
89
90        // This fence ensures guest sees all previous writes. It is matched to a
91        // read barrier in the guest.
92        fence(Ordering::Release);
93
94        write_vmclock_field!(
95            self,
96            mem,
97            disruption_marker,
98            self.inner.disruption_marker.wrapping_add(1)
99        );
100
101        // This fence ensures guest sees the `disruption_marker` update. It is matched to a
102        // read barrier in the guest.
103        fence(Ordering::Release);
104
105        write_vmclock_field!(self, mem, seq_count, self.inner.seq_count.wrapping_add(1));
106    }
107}
108
109/// (De)serialize-able state of the [`VmClock`]
110///
111/// We could avoid this and reuse [`VmClock`] itself if `GuestAddress` was `Serialize`/`Deserialize`
112#[derive(Default, Debug, Clone, Serialize, Deserialize)]
113pub struct VmClockState {
114    /// Guest address in which we write the [`VmClock`] info
115    pub guest_address: u64,
116    /// Data we expose to the guest
117    pub inner: vmclock_abi,
118}
119
120impl<'a> Persist<'a> for VmClock {
121    type State = VmClockState;
122    type ConstructorArgs = &'a GuestMemoryMmap;
123    type Error = Infallible;
124
125    fn save(&self) -> Self::State {
126        VmClockState {
127            guest_address: self.guest_address.0,
128            inner: self.inner,
129        }
130    }
131
132    fn restore(
133        constructor_args: Self::ConstructorArgs,
134        state: &Self::State,
135    ) -> Result<Self, Self::Error> {
136        let mut vmclock = VmClock {
137            guest_address: GuestAddress(state.guest_address),
138            inner: state.inner,
139        };
140        vmclock.post_load_update(constructor_args);
141        Ok(vmclock)
142    }
143}
144
145impl Aml for VmClock {
146    fn append_aml_bytes(&self, v: &mut Vec<u8>) -> Result<(), aml::AmlError> {
147        aml::Device::new(
148            "_SB_.VCLK".try_into()?,
149            vec![
150                &aml::Name::new("_HID".try_into()?, &"AMZNC10C")?,
151                &aml::Name::new("_CID".try_into()?, &"VMCLOCK")?,
152                &aml::Name::new("_DDN".try_into()?, &"VMCLOCK")?,
153                &aml::Method::new(
154                    "_STA".try_into()?,
155                    0,
156                    false,
157                    vec![&aml::Return::new(&0x0fu8)],
158                ),
159                &aml::Name::new(
160                    "_CRS".try_into()?,
161                    &aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
162                        aml::AddressSpaceCacheable::Cacheable,
163                        false,
164                        self.guest_address.0,
165                        self.guest_address.0 + VMCLOCK_SIZE as u64 - 1,
166                    )?]),
167                )?,
168            ],
169        )
170        .append_aml_bytes(v)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use vm_memory::{Bytes, GuestAddress};
177
178    use crate::arch;
179    use crate::devices::acpi::generated::vmclock_abi::vmclock_abi;
180    use crate::devices::acpi::vmclock::{VMCLOCK_SIZE, VmClock};
181    use crate::snapshot::Persist;
182    use crate::test_utils::single_region_mem;
183    use crate::utils::u64_to_usize;
184    use crate::vstate::resources::ResourceAllocator;
185
186    // We are allocating memory from the end of the system memory portion
187    const VMCLOCK_TEST_GUEST_ADDR: GuestAddress =
188        GuestAddress(arch::SYSTEM_MEM_START + arch::SYSTEM_MEM_SIZE - VMCLOCK_SIZE as u64);
189
190    fn default_vmclock() -> VmClock {
191        let mut resource_allocator = ResourceAllocator::new();
192        VmClock::new(&mut resource_allocator)
193    }
194
195    #[test]
196    fn test_new_device() {
197        let vmclock = default_vmclock();
198        let mem = single_region_mem(
199            u64_to_usize(arch::SYSTEM_MEM_START) + u64_to_usize(arch::SYSTEM_MEM_SIZE),
200        );
201
202        let guest_data: vmclock_abi = mem.read_obj(VMCLOCK_TEST_GUEST_ADDR).unwrap();
203        assert_ne!(guest_data, vmclock.inner);
204
205        vmclock.activate(&mem);
206
207        let guest_data: vmclock_abi = mem.read_obj(VMCLOCK_TEST_GUEST_ADDR).unwrap();
208        assert_eq!(guest_data, vmclock.inner);
209    }
210
211    #[test]
212    fn test_device_save_restore() {
213        let vmclock = default_vmclock();
214        let mem = single_region_mem(
215            u64_to_usize(arch::SYSTEM_MEM_START) + u64_to_usize(arch::SYSTEM_MEM_SIZE),
216        );
217
218        vmclock.activate(&mem).unwrap();
219        let guest_data: vmclock_abi = mem.read_obj(VMCLOCK_TEST_GUEST_ADDR).unwrap();
220
221        let state = vmclock.save();
222        let vmclock_new = VmClock::restore(&mem, &state).unwrap();
223
224        let guest_data_new: vmclock_abi = mem.read_obj(VMCLOCK_TEST_GUEST_ADDR).unwrap();
225        assert_ne!(guest_data_new, vmclock.inner);
226        assert_eq!(guest_data_new, vmclock_new.inner);
227        assert_eq!(
228            vmclock.inner.disruption_marker + 1,
229            vmclock_new.inner.disruption_marker
230        );
231    }
232}