1use 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
20const OEM_ID: [u8; 6] = *b"FIRECK";
24
25const OEM_REVISION: u32 = 0;
28
29const HYPERVISOR_VENDOR_ID: [u8; 8] = *b"FIRECKVM";
32
33#[derive(Debug, thiserror::Error, displaydoc::Display)]
34pub enum AcpiError {
36 VmAllocator(#[from] vm_allocator::Error),
38 AcpiTables(#[from] acpi_tables::AcpiError),
40 AmlError(#[from] aml::AmlError),
42}
43
44struct AcpiTableWriter<'a> {
47 mem: &'a GuestMemoryMmap,
48}
49
50impl AcpiTableWriter<'_> {
51 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 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 dsdt_data.extend_from_slice(&device_manager.mmio_devices.dsdt_data);
92
93 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 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 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 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 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 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 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
193pub(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 #[test]
246 fn test_write_acpi_table_memory_allocation() {
247 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 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 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 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 #[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, completed: 259072 },
330 ))
331 ),
332 "{:?}",
333 err
334 );
335 }
336}