vmm/acpi/
mod.rs

1// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use acpi_tables::fadt::{FADT_F_HW_REDUCED_ACPI, FADT_F_PWR_BUTTON, FADT_F_SLP_BUTTON};
5use acpi_tables::{Aml, Dsdt, Fadt, Madt, Mcfg, Rsdp, Sdt, Xsdt, aml};
6use log::{debug, error};
7use vm_allocator::AllocPolicy;
8
9use crate::Vcpu;
10use crate::acpi::x86_64::{
11    apic_addr, rsdp_addr, setup_arch_dsdt, setup_arch_fadt, setup_interrupt_controllers,
12};
13use crate::arch::x86_64::layout;
14use crate::device_manager::DeviceManager;
15use crate::vstate::memory::{GuestAddress, GuestMemoryMmap};
16use crate::vstate::resources::ResourceAllocator;
17
18mod x86_64;
19
20// Our (Original Equipment Manufacturer" (OEM) name. OEM is how ACPI names the manufacturer of the
21// hardware that is exposed to the OS, through ACPI tables. The OEM name is passed in every ACPI
22// table, to let the OS know that we are the owner of the table.
23const OEM_ID: [u8; 6] = *b"FIRECK";
24
25// In reality the OEM revision is per table and it defines the revision of the OEM's implementation
26// of the particular ACPI table. For our purpose, we can set it to a fixed value for all the tables
27const OEM_REVISION: u32 = 0;
28
29// This is needed for an entry in the FADT table. Populating this entry in FADT is a way to let the
30// guest know that it runs within a Firecracker microVM.
31const HYPERVISOR_VENDOR_ID: [u8; 8] = *b"FIRECKVM";
32
33#[derive(Debug, thiserror::Error, displaydoc::Display)]
34/// Error type for ACPI related operations
35pub enum AcpiError {
36    /// Could not allocate resources: {0}
37    VmAllocator(#[from] vm_allocator::Error),
38    /// ACPI tables error: {0}
39    AcpiTables(#[from] acpi_tables::AcpiError),
40    /// Error creating AML bytecode: {0}
41    AmlError(#[from] aml::AmlError),
42}
43
44/// Helper type that holds the guest memory in which we write the tables in and a resource
45/// allocator for allocating space for the tables
46struct AcpiTableWriter<'a> {
47    mem: &'a GuestMemoryMmap,
48}
49
50impl AcpiTableWriter<'_> {
51    /// Write a table in guest memory
52    ///
53    /// This will allocate enough space inside guest memory and write the table in the allocated
54    /// buffer. It returns the address in which it wrote the table.
55    fn write_acpi_table<S>(
56        &mut self,
57        resource_allocator: &mut ResourceAllocator,
58        table: &mut S,
59    ) -> Result<u64, AcpiError>
60    where
61        S: Sdt,
62    {
63        let addr = resource_allocator.allocate_system_memory(
64            table.len().try_into().unwrap(),
65            1,
66            AllocPolicy::FirstMatch,
67        )?;
68
69        table
70            .write_to_guest(self.mem, GuestAddress(addr))
71            .inspect_err(|err| error!("acpi: Could not write table in guest memory: {err}"))?;
72
73        debug!(
74            "acpi: Wrote table ({} bytes) at address: {:#010x}",
75            table.len(),
76            addr
77        );
78
79        Ok(addr)
80    }
81
82    /// Build the DSDT table for the guest
83    fn build_dsdt(
84        &mut self,
85        device_manager: &mut DeviceManager,
86        resource_allocator: &mut ResourceAllocator,
87    ) -> Result<u64, AcpiError> {
88        let mut dsdt_data = Vec::new();
89
90        // Virtio-devices DSDT data
91        dsdt_data.extend_from_slice(&device_manager.mmio_devices.dsdt_data);
92
93        // Add GED and VMGenID AML data.
94        device_manager
95            .acpi_devices
96            .append_aml_bytes(&mut dsdt_data)?;
97
98        if let Some(pci_segment) = &device_manager.pci_devices.pci_segment {
99            pci_segment.append_aml_bytes(&mut dsdt_data)?;
100        }
101
102        // Architecture specific DSDT data
103        setup_arch_dsdt(&mut dsdt_data)?;
104
105        let mut dsdt = Dsdt::new(OEM_ID, *b"FCVMDSDT", OEM_REVISION, dsdt_data);
106        self.write_acpi_table(resource_allocator, &mut dsdt)
107    }
108
109    /// Build the FADT table for the guest
110    ///
111    /// This includes a pointer with the location of the DSDT in guest memory
112    fn build_fadt(
113        &mut self,
114        resource_allocator: &mut ResourceAllocator,
115        dsdt_addr: u64,
116    ) -> Result<u64, AcpiError> {
117        let mut fadt = Fadt::new(OEM_ID, *b"FCVMFADT", OEM_REVISION);
118        fadt.set_hypervisor_vendor_id(HYPERVISOR_VENDOR_ID);
119        fadt.set_x_dsdt(dsdt_addr);
120        fadt.set_flags(
121            (1 << FADT_F_HW_REDUCED_ACPI) | (1 << FADT_F_PWR_BUTTON) | (1 << FADT_F_SLP_BUTTON),
122        );
123        setup_arch_fadt(&mut fadt);
124        self.write_acpi_table(resource_allocator, &mut fadt)
125    }
126
127    /// Build the MADT table for the guest
128    ///
129    /// This includes information about the interrupt controllers supported in the platform
130    fn build_madt(
131        &mut self,
132        resource_allocator: &mut ResourceAllocator,
133        nr_vcpus: u8,
134    ) -> Result<u64, AcpiError> {
135        let mut madt = Madt::new(
136            OEM_ID,
137            *b"FCVMMADT",
138            OEM_REVISION,
139            apic_addr(),
140            setup_interrupt_controllers(nr_vcpus),
141        );
142        self.write_acpi_table(resource_allocator, &mut madt)
143    }
144
145    /// Build the XSDT table for the guest
146    ///
147    /// Currently, we pass to the guest just FADT and MADT tables.
148    fn build_xsdt(
149        &mut self,
150        resource_allocator: &mut ResourceAllocator,
151        fadt_addr: u64,
152        madt_addr: u64,
153        mcfg_addr: u64,
154    ) -> Result<u64, AcpiError> {
155        let mut xsdt = Xsdt::new(
156            OEM_ID,
157            *b"FCMVXSDT",
158            OEM_REVISION,
159            vec![fadt_addr, madt_addr, mcfg_addr],
160        );
161        self.write_acpi_table(resource_allocator, &mut xsdt)
162    }
163
164    /// Build the MCFG table for the guest.
165    fn build_mcfg(
166        &mut self,
167        resource_allocator: &mut ResourceAllocator,
168        pci_mmio_config_addr: u64,
169    ) -> Result<u64, AcpiError> {
170        let mut mcfg = Mcfg::new(OEM_ID, *b"FCMVMCFG", OEM_REVISION, pci_mmio_config_addr);
171        self.write_acpi_table(resource_allocator, &mut mcfg)
172    }
173
174    /// Build the RSDP pointer for the guest.
175    ///
176    /// This will build the RSDP pointer which points to the XSDT table and write it in guest
177    /// memory. The address in which we write RSDP is pre-determined for every architecture.
178    /// We will not allocate arbitrary memory for it
179    fn build_rsdp(&mut self, xsdt_addr: u64) -> Result<(), AcpiError> {
180        let mut rsdp = Rsdp::new(OEM_ID, xsdt_addr);
181        rsdp.write_to_guest(self.mem, rsdp_addr())
182            .inspect_err(|err| error!("acpi: Could not write RSDP in guest memory: {err}"))?;
183
184        debug!(
185            "acpi: Wrote RSDP ({} bytes) at address: {:#010x}",
186            rsdp.len(),
187            rsdp_addr().0
188        );
189        Ok(())
190    }
191}
192
193/// Create ACPI tables for the guest
194///
195/// This will create the ACPI tables needed to describe to the guest OS the available hardware,
196/// such as interrupt controllers, vCPUs and VirtIO devices.
197pub(crate) fn create_acpi_tables(
198    mem: &GuestMemoryMmap,
199    device_manager: &mut DeviceManager,
200    resource_allocator: &mut ResourceAllocator,
201    vcpus: &[Vcpu],
202) -> Result<(), AcpiError> {
203    let mut writer = AcpiTableWriter { mem };
204    let dsdt_addr = writer.build_dsdt(device_manager, resource_allocator)?;
205
206    let fadt_addr = writer.build_fadt(resource_allocator, dsdt_addr)?;
207    let madt_addr = writer.build_madt(resource_allocator, vcpus.len().try_into().unwrap())?;
208    let mcfg_addr = writer.build_mcfg(resource_allocator, layout::PCI_MMCONFIG_START)?;
209    let xsdt_addr = writer.build_xsdt(resource_allocator, fadt_addr, madt_addr, mcfg_addr)?;
210    writer.build_rsdp(xsdt_addr)
211}
212
213#[cfg(test)]
214mod tests {
215    use acpi_tables::Sdt;
216    use vm_memory::Bytes;
217
218    use crate::acpi::{AcpiError, AcpiTableWriter};
219    use crate::arch::x86_64::layout::{SYSTEM_MEM_SIZE, SYSTEM_MEM_START};
220    use crate::builder::tests::default_vmm;
221    use crate::utils::u64_to_usize;
222    use crate::vstate::resources::ResourceAllocator;
223    use crate::vstate::vm::tests::setup_vm_with_memory;
224
225    struct MockSdt(Vec<u8>);
226
227    impl Sdt for MockSdt {
228        fn len(&self) -> usize {
229            self.0.len()
230        }
231
232        fn write_to_guest<M: vm_memory::GuestMemory>(
233            &mut self,
234            mem: &M,
235            address: vm_memory::GuestAddress,
236        ) -> acpi_tables::Result<()> {
237            mem.write_slice(&self.0, address)?;
238            Ok(())
239        }
240    }
241
242    // Currently we are allocating up to SYSTEM_MEM_SIZE memory for ACPI tables. We are allocating
243    // using the FirstMatch policy, with an 1 byte alignment. This test checks that we are able to
244    // allocate up to this size, and get back the expected addresses.
245    #[test]
246    fn test_write_acpi_table_memory_allocation() {
247        // A mocke Vmm object with 128MBs of memory
248        let vmm = default_vmm();
249        let mut writer = AcpiTableWriter {
250            mem: vmm.vm.guest_memory(),
251        };
252        let mut resource_allocator = vmm.vm.resource_allocator();
253
254        // This should succeed
255        let mut sdt = MockSdt(vec![0; 4096]);
256        let addr = writer
257            .write_acpi_table(&mut resource_allocator, &mut sdt)
258            .unwrap();
259        assert_eq!(addr, SYSTEM_MEM_START);
260
261        // Let's try to write two 4K pages plus one byte
262        let mut sdt = MockSdt(vec![0; usize::try_from(SYSTEM_MEM_SIZE + 1).unwrap()]);
263        let err = writer
264            .write_acpi_table(&mut resource_allocator, &mut sdt)
265            .unwrap_err();
266        assert!(
267            matches!(
268                err,
269                AcpiError::VmAllocator(vm_allocator::Error::ResourceNotAvailable)
270            ),
271            "{:?}",
272            err
273        );
274
275        // We are allocating memory for tables with alignment of 1 byte. All of these should
276        // succeed.
277        let mut sdt = MockSdt(vec![0; 5]);
278        let addr = writer
279            .write_acpi_table(&mut resource_allocator, &mut sdt)
280            .unwrap();
281        assert_eq!(addr, SYSTEM_MEM_START + 4096);
282        let mut sdt = MockSdt(vec![0; 2]);
283        let addr = writer
284            .write_acpi_table(&mut resource_allocator, &mut sdt)
285            .unwrap();
286        assert_eq!(addr, SYSTEM_MEM_START + 4101);
287        let mut sdt = MockSdt(vec![0; 4]);
288        let addr = writer
289            .write_acpi_table(&mut resource_allocator, &mut sdt)
290            .unwrap();
291        assert_eq!(addr, SYSTEM_MEM_START + 4103);
292        let mut sdt = MockSdt(vec![0; 8]);
293        let addr = writer
294            .write_acpi_table(&mut resource_allocator, &mut sdt)
295            .unwrap();
296        assert_eq!(addr, SYSTEM_MEM_START + 4107);
297        let mut sdt = MockSdt(vec![0; 16]);
298        let addr = writer
299            .write_acpi_table(&mut resource_allocator, &mut sdt)
300            .unwrap();
301        assert_eq!(addr, SYSTEM_MEM_START + 4115);
302    }
303
304    // If, for whatever weird reason, we end up with microVM that has less memory than the maximum
305    // address we allocate for ACPI tables, we would be able to allocate the tables but we would
306    // not be able to write them. This is practically impossible in our case. If we get such a
307    // guest memory, we won't be able to load the guest kernel, but the function does
308    // return an error on this case, so let's just check that in case any of these assumptions
309    // change in the future.
310    #[test]
311    fn test_write_acpi_table_small_memory() {
312        let (_, vm) = setup_vm_with_memory(u64_to_usize(SYSTEM_MEM_START + SYSTEM_MEM_SIZE - 4096));
313        let mut writer = AcpiTableWriter {
314            mem: vm.guest_memory(),
315        };
316        let mut resource_allocator = ResourceAllocator::new();
317
318        let mut sdt = MockSdt(vec![0; usize::try_from(SYSTEM_MEM_SIZE).unwrap()]);
319        let err = writer
320            .write_acpi_table(&mut resource_allocator, &mut sdt)
321            .unwrap_err();
322        assert!(
323            matches!(
324                err,
325                AcpiError::AcpiTables(acpi_tables::AcpiError::GuestMemory(
326                    vm_memory::GuestMemoryError::PartialBuffer {
327                        expected: 263168,  // SYSTEM_MEM_SIZE
328                        completed: 259072  // SYSTEM_MEM_SIZE - 4096
329                    },
330                ))
331            ),
332            "{:?}",
333            err
334        );
335    }
336}