Skip to content

Commit 61f0ed4

Browse files
committed
Wire up ACPI table generation via fw_cfg
Integrate the new ACPI table generation into propolis-standalone and propolis-server. Also replace hardcoded memory region addresses with constants that align with ACPI table definitions. The PCIe ECAM base is kept same as before at 0xe000_0000 (3.5GB) to match existing i440fx chipset ECAM placement. ECAM is no longer added to the E820 map as reserved memory since it is MMIO space properly described in the MCFG ACPI table. Guest physical memory map: 0x0000_0000 - 0xbfff_ffff Low RAM (up to 3 GiB) 0xc000_0000 - 0xffff_ffff PCI hole (1 GiB MMIO region) 0xc000_0000 - 0xdfff_ffff 32-bit PCI MMIO 0xe000_0000 - 0xefff_ffff PCIe ECAM (256 MiB, 256 buses) 0xfec0_0000 IOAPIC 0xfed0_0000 HPET 0xffe0_0000 - 0xffff_ffff Bootrom (2 MiB) 0x1_0000_0000+ High RAM + 64-bit PCI MMIO e820 map as seen by guest: 0x0000_0000 - 0x0009_ffff Usable (640 KiB low memory) 0x0010_0000 - 0xbeaf_ffff Usable (~3 GiB main RAM) 0xbeb0_0000 - 0xbfb6_cfff Reserved (UEFI runtime/data) 0xbfb6_d000 - 0xbfbf_efff ACPI Tables + NVS 0xbfbf_f000 - 0xbffd_ffff Usable (top of low memory) 0xbffe_0000 - 0xffff_ffff Reserved (PCI hole) 0x1_0000_0000 - highmem Usable (high RAM above 4 GiB) To stay on safe side only enable using new ACPI tables for newly launched VMs. Old VMs using OVMF tables would keep using the same OVMF tables throughout multiple migrations. To verify this add the phd test as well for new VM launched with native tables, native tables preserved through migration and VM launched from old propolis without native tables stays with OVMF through multiple future migrations. Signed-off-by: Amey Narkhede <[email protected]> Signed-off-by: glitzflitz <[email protected]>
1 parent 7036a3e commit 61f0ed4

File tree

12 files changed

+317
-14
lines changed

12 files changed

+317
-14
lines changed

bin/propolis-cli/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ impl VmConfig {
340340
} else {
341341
Default::default()
342342
},
343+
native_acpi_tables: Some(true),
343344
},
344345
components: Default::default(),
345346
smbios: None,

bin/propolis-server/src/lib/initializer.rs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ pub enum MachineInitError {
111111
/// Arbitrary ROM limit for now
112112
const MAX_ROM_SIZE: usize = 0x20_0000;
113113

114+
const PCIE_ECAM_BASE: usize = 0xe000_0000;
115+
const PCIE_ECAM_SIZE: usize = 0x1000_0000;
116+
const MEM_32BIT_DEVICES_START: usize = 0xc000_0000;
117+
const MEM_32BIT_DEVICES_END: usize = 0xfc00_0000;
118+
const HIGHMEM_START: usize = 0x1_0000_0000;
119+
114120
fn get_spec_guest_ram_limits(spec: &Spec) -> (usize, usize) {
115121
let memsize = spec.board.memory_mb as usize * MB;
116122
let lowmem = memsize.min(3 * GB);
@@ -141,19 +147,22 @@ pub fn build_instance(
141147
.context("failed to add low memory region")?
142148
.add_rom_region(0x1_0000_0000 - MAX_ROM_SIZE, MAX_ROM_SIZE, "bootrom")
143149
.context("failed to add bootrom region")?
144-
.add_mmio_region(0xc000_0000_usize, 0x2000_0000_usize, "dev32")
150+
.add_mmio_region(lowmem, PCIE_ECAM_BASE - lowmem, "dev32")
145151
.context("failed to add low device MMIO region")?
146-
.add_mmio_region(0xe000_0000_usize, 0x1000_0000_usize, "pcicfg")
152+
.add_mmio_region(
153+
PCIE_ECAM_BASE,
154+
MEM_32BIT_DEVICES_END - PCIE_ECAM_BASE,
155+
"pcicfg",
156+
)
147157
.context("failed to add PCI config region")?;
148158

149-
let highmem_start = 0x1_0000_0000;
150159
if highmem > 0 {
151160
builder = builder
152-
.add_mem_region(highmem_start, highmem, "highmem")
161+
.add_mem_region(HIGHMEM_START, highmem, "highmem")
153162
.context("failed to add high memory region")?;
154163
}
155164

156-
let dev64_start = highmem_start + highmem;
165+
let dev64_start = HIGHMEM_START + highmem;
157166
builder = builder
158167
.add_mmio_region(dev64_start, vmm::MAX_PHYSMEM - dev64_start, "dev64")
159168
.context("failed to add high device MMIO region")?;
@@ -1170,9 +1179,17 @@ impl MachineInitializer<'_> {
11701179
propolis::vmm::MapType::Dram => {
11711180
e820_table.add_mem(addr, len);
11721181
}
1173-
_ => {
1182+
propolis::vmm::MapType::Rom => {
11741183
e820_table.add_reserved(addr, len);
11751184
}
1185+
propolis::vmm::MapType::Mmio => {
1186+
// With native ACPI tables, MMIO is described in the DSDT
1187+
// _CRS and should not appear in E820. Without native
1188+
// tables, preserve original E820 layout.
1189+
if self.spec.board.native_acpi_tables != Some(true) {
1190+
e820_table.add_reserved(addr, len);
1191+
}
1192+
}
11761193
}
11771194
}
11781195

@@ -1284,6 +1301,66 @@ impl MachineInitializer<'_> {
12841301
.insert_named("etc/e820", e820_entry)
12851302
.map_err(|e| MachineInitError::FwcfgInsertFailed("e820", e))?;
12861303

1304+
if self.spec.board.native_acpi_tables == Some(true) {
1305+
let (_, highmem) = get_spec_guest_ram_limits(self.spec);
1306+
let dev64_start = HIGHMEM_START + highmem;
1307+
1308+
// Collect DSDT generators from devices that implement the trait
1309+
let generators: Vec<_> = self
1310+
.devices
1311+
.values()
1312+
.filter_map(|dev| dev.as_dsdt_generator())
1313+
.collect();
1314+
1315+
// Get the physical address width from CPUID leaf 0x8000_0008.
1316+
// EAX[7:0] contains the physical address bits supported by the CPU.
1317+
// The 64-bit MMIO limit must not exceed what the CPU can address.
1318+
let phys_addr_bits = self
1319+
.spec
1320+
.cpuid
1321+
.get(CpuidIdent::leaf(0x8000_0008))
1322+
.map(|v| v.eax & 0xff)
1323+
.unwrap_or(48) as u64;
1324+
let max_phys_addr = (1u64 << phys_addr_bits) - 1;
1325+
let mmio64_limit =
1326+
max_phys_addr.min(vmm::MAX_PHYSMEM as u64 - 1);
1327+
1328+
let acpi_tables = fwcfg::formats::build_acpi_tables(
1329+
&fwcfg::formats::AcpiConfig {
1330+
num_cpus: cpus,
1331+
pcie_ecam_base: PCIE_ECAM_BASE as u64,
1332+
pcie_ecam_size: PCIE_ECAM_SIZE as u64,
1333+
pcie_mmio32_base: MEM_32BIT_DEVICES_START as u64,
1334+
pcie_mmio32_limit: (MEM_32BIT_DEVICES_END - 1) as u64,
1335+
pcie_mmio64_base: dev64_start as u64,
1336+
pcie_mmio64_limit: mmio64_limit,
1337+
..Default::default()
1338+
},
1339+
&generators,
1340+
);
1341+
fwcfg
1342+
.insert_named(
1343+
"etc/acpi/tables",
1344+
Entry::Bytes(acpi_tables.tables),
1345+
)
1346+
.map_err(|e| {
1347+
MachineInitError::FwcfgInsertFailed("acpi/tables", e)
1348+
})?;
1349+
fwcfg
1350+
.insert_named("etc/acpi/rsdp", Entry::Bytes(acpi_tables.rsdp))
1351+
.map_err(|e| {
1352+
MachineInitError::FwcfgInsertFailed("acpi/rsdp", e)
1353+
})?;
1354+
fwcfg
1355+
.insert_named(
1356+
"etc/table-loader",
1357+
Entry::Bytes(acpi_tables.loader),
1358+
)
1359+
.map_err(|e| {
1360+
MachineInitError::FwcfgInsertFailed("table-loader", e)
1361+
})?;
1362+
}
1363+
12871364
let ramfb = ramfb::RamFb::create(
12881365
self.log.new(slog::o!("component" => "ramfb")),
12891366
);

bin/propolis-server/src/lib/server.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,14 @@ impl PropolisServerApi for PropolisServerImpl {
248248
let vm_init = match init {
249249
InstanceInitializationMethod::Spec { spec } => spec
250250
.try_into()
251-
.map(|s| VmInitializationMethod::Spec(Box::new(s)))
251+
.map(|mut s: crate::spec::Spec| {
252+
// Default to native ACPI tables for new VMs but respect
253+
// explicit client preference if provided.
254+
if s.board.native_acpi_tables.is_none() {
255+
s.board.native_acpi_tables = Some(true);
256+
}
257+
VmInitializationMethod::Spec(Box::new(s))
258+
})
252259
.map_err(|e| {
253260
if let Some(s) = e.source() {
254261
format!("{e}: {s}")
@@ -337,7 +344,14 @@ impl PropolisServerApi for PropolisServerImpl {
337344
let vm_init = match init {
338345
InstanceInitializationMethodV0::Spec { spec } => spec
339346
.try_into()
340-
.map(|s| VmInitializationMethod::Spec(Box::new(s)))
347+
.map(|mut s: crate::spec::Spec| {
348+
// Default to native ACPI tables for new VMs, but respect
349+
// explicit client preference if provided.
350+
if s.board.native_acpi_tables.is_none() {
351+
s.board.native_acpi_tables = Some(true);
352+
}
353+
VmInitializationMethod::Spec(Box::new(s))
354+
})
341355
.map_err(|e| {
342356
if let Some(s) = e.source() {
343357
format!("{e}: {s}")

bin/propolis-server/src/lib/spec/api_spec_v0.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ impl From<Spec> for InstanceSpecV0 {
101101
chipset: board.chipset,
102102
guest_hv_interface: board.guest_hv_interface,
103103
cpuid: Some(cpuid.into_instance_spec_cpuid()),
104+
native_acpi_tables: board.native_acpi_tables,
104105
};
105106
let mut spec = InstanceSpecV0 { board, components: Default::default() };
106107

bin/propolis-server/src/lib/spec/builder.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ impl SpecBuilder {
9696
memory_mb: board.memory_mb,
9797
chipset: board.chipset,
9898
guest_hv_interface: board.guest_hv_interface,
99+
native_acpi_tables: board.native_acpi_tables,
99100
},
100101
cpuid,
101102
..Default::default()
@@ -380,6 +381,7 @@ mod test {
380381
memory_mb: 512,
381382
chipset: Chipset::I440Fx(I440Fx { enable_pcie: false }),
382383
guest_hv_interface: GuestHypervisorInterface::Bhyve,
384+
native_acpi_tables: Some(false),
383385
};
384386

385387
SpecBuilder {

bin/propolis-server/src/lib/spec/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ pub(crate) struct Board {
129129
pub memory_mb: u64,
130130
pub chipset: Chipset,
131131
pub guest_hv_interface: GuestHypervisorInterface,
132+
/// Use native ACPI tables instead of OVMF-provided tables.
133+
///
134+
/// `None` indicates a VM created before native ACPI table support existed.
135+
/// For migration compatibility, `None` is preserved through round-trips
136+
/// and treated the same as `Some(false)` at runtime.
137+
pub native_acpi_tables: Option<bool>,
132138
}
133139

134140
impl Default for Board {
@@ -138,6 +144,7 @@ impl Default for Board {
138144
memory_mb: 0,
139145
chipset: Chipset::I440Fx(I440Fx { enable_pcie: false }),
140146
guest_hv_interface: GuestHypervisorInterface::Bhyve,
147+
native_acpi_tables: None,
141148
}
142149
}
143150
}

bin/propolis-standalone/src/main.rs

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ const PAGE_OFFSET: u64 = 0xfff;
4343
// Arbitrary ROM limit for now
4444
const MAX_ROM_SIZE: usize = 0x20_0000;
4545

46+
const PCIE_ECAM_BASE: usize = 0xe000_0000;
47+
const PCIE_ECAM_SIZE: usize = 0x1000_0000;
48+
const MEM_32BIT_DEVICES_START: usize = 0xc000_0000;
49+
const MEM_32BIT_DEVICES_END: usize = 0xfc00_0000;
50+
const HIGHMEM_START: usize = 0x1_0000_0000;
51+
4652
const MIN_RT_THREADS: usize = 8;
4753
const BASE_RT_THREADS: usize = 4;
4854

@@ -738,15 +744,18 @@ fn build_machine(
738744
.max_cpus(max_cpu)?
739745
.add_mem_region(0, lowmem, "lowmem")?
740746
.add_rom_region(0x1_0000_0000 - MAX_ROM_SIZE, MAX_ROM_SIZE, "bootrom")?
741-
.add_mmio_region(0xc000_0000, 0x2000_0000, "dev32")?
742-
.add_mmio_region(0xe000_0000, 0x1000_0000, "pcicfg")?;
747+
.add_mmio_region(lowmem, PCIE_ECAM_BASE - lowmem, "dev32")?
748+
.add_mmio_region(
749+
PCIE_ECAM_BASE,
750+
MEM_32BIT_DEVICES_END - PCIE_ECAM_BASE,
751+
"pcicfg",
752+
)?;
743753

744-
let highmem_start = 0x1_0000_0000;
745754
if highmem > 0 {
746-
builder = builder.add_mem_region(highmem_start, highmem, "highmem")?;
755+
builder = builder.add_mem_region(HIGHMEM_START, highmem, "highmem")?;
747756
}
748757

749-
let dev64_start = highmem_start + highmem;
758+
let dev64_start = HIGHMEM_START + highmem;
750759
builder = builder.add_mmio_region(
751760
dev64_start,
752761
vmm::MAX_PHYSMEM - dev64_start,
@@ -974,9 +983,13 @@ fn generate_e820(
974983
MapType::Dram => {
975984
e820_table.add_mem(addr, len);
976985
}
977-
_ => {
986+
MapType::Rom => {
978987
e820_table.add_reserved(addr, len);
979988
}
989+
MapType::Mmio => {
990+
// MMIO is described in the DSDT _CRS and should not
991+
// appear in E820.
992+
}
980993
}
981994
}
982995

@@ -1395,6 +1408,54 @@ fn setup_instance(
13951408
let e820_entry = generate_e820(machine, log).expect("can build E820 table");
13961409
fwcfg.insert_named("etc/e820", e820_entry).unwrap();
13971410

1411+
// Collect DSDT generators from devices that implement the trait
1412+
let generators: Vec<_> = guard
1413+
.inventory
1414+
.devs
1415+
.values()
1416+
.filter_map(|dev| dev.as_dsdt_generator())
1417+
.collect();
1418+
1419+
// Get the physical address width from CPUID leaf 0x8000_0008.
1420+
// EAX[7:0] contains the physical address bits supported by the CPU.
1421+
// The 64-bit MMIO limit must not exceed what the CPU can address.
1422+
let phys_addr_bits = cpuid_profile
1423+
.as_ref()
1424+
.and_then(|p| p.get(CpuidIdent::leaf(0x8000_0008)))
1425+
.map(|v| v.eax & 0xff)
1426+
.unwrap_or(48) as u64;
1427+
let max_phys_addr = (1u64 << phys_addr_bits) - 1;
1428+
let mmio64_limit = max_phys_addr.min(vmm::MAX_PHYSMEM as u64 - 1);
1429+
1430+
let acpi_tables = fwcfg::formats::build_acpi_tables(
1431+
&fwcfg::formats::AcpiConfig {
1432+
num_cpus: cpus,
1433+
pcie_ecam_base: PCIE_ECAM_BASE as u64,
1434+
pcie_ecam_size: PCIE_ECAM_SIZE as u64,
1435+
pcie_mmio32_base: MEM_32BIT_DEVICES_START as u64,
1436+
pcie_mmio32_limit: (MEM_32BIT_DEVICES_END - 1) as u64,
1437+
pcie_mmio64_base: (HIGHMEM_START + highmem) as u64,
1438+
pcie_mmio64_limit: mmio64_limit,
1439+
..Default::default()
1440+
},
1441+
&generators,
1442+
);
1443+
fwcfg
1444+
.insert_named(
1445+
"etc/acpi/tables",
1446+
fwcfg::Entry::Bytes(acpi_tables.tables),
1447+
)
1448+
.context("failed to insert ACPI tables")?;
1449+
fwcfg
1450+
.insert_named("etc/acpi/rsdp", fwcfg::Entry::Bytes(acpi_tables.rsdp))
1451+
.context("failed to insert ACPI RSDP")?;
1452+
fwcfg
1453+
.insert_named(
1454+
"etc/table-loader",
1455+
fwcfg::Entry::Bytes(acpi_tables.loader),
1456+
)
1457+
.context("failed to insert ACPI table-loader")?;
1458+
13981459
fwcfg.attach(pio, &machine.acc_mem);
13991460

14001461
guard.inventory.register(&fwcfg);

crates/propolis-api-types/src/instance_spec/components/board.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,5 +178,14 @@ pub struct Board {
178178
/// default values from the host's CPUID values.
179179
#[serde(default, skip_serializing_if = "Option::is_none")]
180180
pub cpuid: Option<Cpuid>,
181+
182+
/// Use native ACPI tables (via fw_cfg) instead of OVMF provided tables.
183+
///
184+
/// VMs created before propolis supported ACPI table generation will not
185+
/// have this field. For backwards compatibility with live migration,
186+
/// `None` is treated as `false` (use OVMF tables). New VMs should set
187+
/// this to `true`.
188+
#[serde(default, skip_serializing_if = "Option::is_none")]
189+
pub native_acpi_tables: Option<bool>,
181190
// TODO: Processor and NUMA topology.
182191
}

phd-tests/framework/src/test_vm/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub struct VmConfig<'dr> {
5656
disks: Vec<DiskRequest<'dr>>,
5757
migration_failure: Option<MigrationFailureInjector>,
5858
guest_hv_interface: Option<GuestHypervisorInterface>,
59+
native_acpi_tables: Option<bool>,
5960
}
6061

6162
impl<'dr> VmConfig<'dr> {
@@ -76,6 +77,7 @@ impl<'dr> VmConfig<'dr> {
7677
disks: Vec::new(),
7778
migration_failure: None,
7879
guest_hv_interface: None,
80+
native_acpi_tables: Some(true),
7981
};
8082

8183
config.boot_disk(
@@ -151,6 +153,11 @@ impl<'dr> VmConfig<'dr> {
151153
self
152154
}
153155

156+
pub fn native_acpi_tables(&mut self, enabled: Option<bool>) -> &mut Self {
157+
self.native_acpi_tables = enabled;
158+
self
159+
}
160+
154161
/// Add a new disk to the VM config, and add it to the front of the VM's
155162
/// boot order.
156163
///
@@ -221,6 +228,7 @@ impl<'dr> VmConfig<'dr> {
221228
disks,
222229
migration_failure,
223230
guest_hv_interface,
231+
native_acpi_tables,
224232
} = self;
225233

226234
let bootrom_path = framework
@@ -302,6 +310,7 @@ impl<'dr> VmConfig<'dr> {
302310
.as_ref()
303311
.cloned()
304312
.unwrap_or_default(),
313+
native_acpi_tables: *native_acpi_tables,
305314
},
306315
components: Default::default(),
307316
smbios: None,

0 commit comments

Comments
 (0)