Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4bd4d97
Add fw_cfg table-loader helpers for ACPI table generation
glitzflitz Dec 29, 2025
e92e733
Add RSDT, XSDT and RSDP tables
glitzflitz Dec 29, 2025
edaa5f9
Add FADT and DSDT table generation
glitzflitz Dec 29, 2025
911f322
Add MADT table
glitzflitz Dec 29, 2025
67efebe
Add MCFG and HPET tables
glitzflitz Dec 29, 2025
f1d31ef
Add FACS table
glitzflitz Dec 29, 2025
22ff73b
Define AML opcode constants
glitzflitz Dec 29, 2025
7e49d92
Add ACPI name encoding utilities
glitzflitz Dec 29, 2025
182ad92
Introduce AML bytecode generation
glitzflitz Dec 29, 2025
076da97
Support resource construction for ACPI methods
glitzflitz Dec 29, 2025
7f4d952
Wire up firmware/acpi module exports
glitzflitz Dec 29, 2025
79b5163
Generate DSDT with PCIe host bridge
glitzflitz Dec 30, 2025
f607273
Implement DsdtGenerator for LpcUart
glitzflitz Jan 4, 2026
accf23a
Add PS/2 controller in DSDT
glitzflitz Dec 30, 2025
4dbea1a
Add Qemu pvpanic device to DSDT
glitzflitz Jan 4, 2026
99145a1
Add PCIe _OSC method for OS capability negotiation
glitzflitz Dec 30, 2025
1d38374
Prepare the ACPI tables for generation
glitzflitz Jan 4, 2026
43c454c
Wire up ACPI table generation via fw_cfg
glitzflitz Dec 31, 2025
748782e
acpi: generate ACPI tables using acpi_tables crate
lgfa29 Mar 16, 2026
482f02d
docs: minor docs touch-ups
lgfa29 Apr 20, 2026
872fc14
fix clippy
lgfa29 Apr 20, 2026
adb066c
minor fixes
lgfa29 Apr 21, 2026
188f01b
tests: add phd-test for ACPI tables
lgfa29 Apr 21, 2026
d736f0e
fix clippy
lgfa29 Apr 21, 2026
901ac12
fwcg: handle invalid configuration
lgfa29 Apr 22, 2026
9aa7292
minor fixes and more tests
lgfa29 Apr 22, 2026
c1788c1
minor fixes and improvements
lgfa29 Apr 23, 2026
303f9b2
acpi: add more shared constants b/w fadt and dsdt
lgfa29 Apr 30, 2026
a821bb9
acpi: add test for ACPI table generation
lgfa29 May 21, 2026
109fcc0
acpi: use stable references to ACPI names
lgfa29 May 20, 2026
c6ff064
acpi: update comments in DSDT table generation
lgfa29 May 22, 2026
7005c07
acpi: document bhyve and unhandled IO ports
lgfa29 May 22, 2026
a6d7fe3
acpi: expand documentation for sleep states
lgfa29 May 22, 2026
ea59a16
acpi: wrapper for IO port declaration
lgfa29 May 22, 2026
0c11673
acpi: add historical note about ACPI tables
lgfa29 May 22, 2026
fe68dc7
acpi: reuse existing IO port values
lgfa29 May 22, 2026
7444ca7
acpi: use SERIALIZED and NOT_SERIALIZED constants
lgfa29 May 22, 2026
c76db95
acpi: expand on MMIO32 and MMIO64 range values
lgfa29 May 23, 2026
2495c6c
acpi: update lpc.rs
lgfa29 May 25, 2026
1b3840b
acpi: update fadt.rs
lgfa29 May 26, 2026
61b6218
acpi: expand doc on ACPI reset register
lgfa29 May 26, 2026
2b49bbf
acpi: add note about high vCPU count
lgfa29 May 26, 2026
75c626b
acpi: reuse more existing values and improve docs
lgfa29 May 26, 2026
223f48d
acpi: update fwcfg.rs
lgfa29 May 26, 2026
02bb8f7
deps: use tag for acpi_tables crate
lgfa29 May 26, 2026
f0fa02b
test: document `acpi_tables_parse` guest image
lgfa29 May 26, 2026
bada383
acpi: minor updates and expanded docs
lgfa29 May 27, 2026
dab156c
acpi: add device metadata storage
lgfa29 May 28, 2026
da9fa2f
acpi: reorganize DSDT and SSDT table generation
lgfa29 Jun 2, 2026
810bd49
acpi: add `acpi_variant` and constructors
lgfa29 Jun 2, 2026
49ef26a
acpi: fix test
lgfa29 Jun 2, 2026
bb8daf8
acpi: update device metadata api
lgfa29 Jun 3, 2026
36bf455
acpi: remove the use of default
lgfa29 Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
debug.out
core
out/

# Ignore all binary and decompiled ACPI files except the ones used for tests.
*.dat
*.dsl
!phd-tests/tests/testdata/acpi/**/*.dat
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dice-verifier = { git = "https://github.com/oxidecomputer/dice-util", rev = "d74
vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "deaf0203ecfa8cbe8c0c28c9bd16645d5d7be4e8", default-features = false }

# External dependencies
acpi_tables = "0.2.0"
anyhow = "1.0"
async-trait = "0.1.88"
atty = "0.2.14"
Expand Down Expand Up @@ -192,6 +193,8 @@ usdt = { version = "0.6", default-features = false }
uuid = "1.3.2"
zerocopy = "0.8.25"

[patch.crates-io]
acpi_tables = { git = 'https://github.com/oxidecomputer/acpi_tables.git', tag = "v0.2.1-oxide.1" }

#
# It's common during development to use a local copy of various complex
Expand Down
1 change: 1 addition & 0 deletions bin/propolis-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ proptest.workspace = true

[features]
default = []
acpi-debug = ["propolis/acpi-debug"]

# When building to be packaged for inclusion in the production ramdisk
# (nominally an Omicron package), certain code is compiled in or out.
Expand Down
103 changes: 93 additions & 10 deletions bin/propolis-server/src/lib/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ use propolis::attestation::server::AttestationServerConfig;
use propolis::attestation::server::AttestationSock;
use propolis::block;
use propolis::chardev::{self, BlockingSource, Source};
use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE};
use propolis::common::{DeviceMetadataMap, Lifecycle, GB, MB, PAGE_SIZE};
use propolis::cpuid::TopoKind;
use propolis::enlightenment::Enlightenment;
use propolis::firmware::smbios;
use propolis::firmware::{acpi, smbios};
use propolis::hw::bhyve::BhyveHpet;
use propolis::hw::chipset::{i440fx, Chipset};
use propolis::hw::ibmpc;
Expand All @@ -45,7 +45,7 @@ use propolis::hw::qemu::{
fwcfg::{self, Entry},
ramfb,
};
use propolis::hw::uart::LpcUart;
use propolis::hw::uart::{LpcUart, LpcUartMetadata};
use propolis::hw::{nvme, virtio};
use propolis::intr_pins;
use propolis::vmm::{self, Builder, Machine};
Expand Down Expand Up @@ -107,6 +107,9 @@ pub enum MachineInitError {
#[error("boot entry {0:?} refers to a device on non-zero PCI bus {1}")]
BootDeviceOnDownstreamPciBus(SpecKey, u8),

#[error("failed to generate ACPI tables: {0}")]
AcpiTableError(#[from] fwcfg::formats::AcpiTablesError),

#[error("failed to insert {0} fwcfg entry")]
FwcfgInsertFailed(&'static str, #[source] fwcfg::InsertError),

Expand All @@ -124,6 +127,15 @@ pub enum MachineInitError {
/// Arbitrary ROM limit for now
const MAX_ROM_SIZE: usize = 0x20_0000;

/// End address of the 32-bit PCI MMIO window.
///
// Value inherited from the original EDK2 static tables.
// https://github.com/oxidecomputer/edk2/blob/f33871f488bfbbc080e0f7e3881e04d0db0b6367/OvmfPkg/PlatformPei/Platform.c#L180-L192
//
// It should be updated to match the actual memory regions registered in the
// instance.
const PCI_MMIO32_END: usize = 0xfeef_ffff;

fn get_spec_guest_ram_limits(spec: &Spec) -> (usize, usize) {
let memsize = spec.board.memory_mb as usize * MB;
let lowmem = memsize.min(3 * GB);
Expand Down Expand Up @@ -204,6 +216,7 @@ pub struct MachineInitializer<'a> {
pub(crate) log: slog::Logger,
pub(crate) machine: &'a Machine,
pub(crate) devices: DeviceMap,
pub(crate) device_metadata: DeviceMetadataMap,
pub(crate) block_backends: BlockBackendMap,
pub(crate) crucible_backends: CrucibleBackendMap,
pub(crate) spec: &'a Spec,
Expand Down Expand Up @@ -406,17 +419,28 @@ impl MachineInitializer<'_> {
continue;
}

let (irq, port) = match desc.num {
SerialPortNumber::Com1 => (ibmpc::IRQ_COM1, ibmpc::PORT_COM1),
SerialPortNumber::Com2 => (ibmpc::IRQ_COM2, ibmpc::PORT_COM2),
SerialPortNumber::Com3 => (ibmpc::IRQ_COM3, ibmpc::PORT_COM3),
SerialPortNumber::Com4 => (ibmpc::IRQ_COM4, ibmpc::PORT_COM4),
let (num, irq, port) = match desc.num {
SerialPortNumber::Com1 => {
(1, ibmpc::IRQ_COM1, ibmpc::PORT_COM1)
}
SerialPortNumber::Com2 => {
(2, ibmpc::IRQ_COM2, ibmpc::PORT_COM2)
}
SerialPortNumber::Com3 => {
(3, ibmpc::IRQ_COM3, ibmpc::PORT_COM3)
}
SerialPortNumber::Com4 => {
(4, ibmpc::IRQ_COM4, ibmpc::PORT_COM4)
}
};

let dev = LpcUart::new(chipset.irq_pin(irq).unwrap());
dev.set_autodiscard(true);
LpcUart::attach(&dev, &self.machine.bus_pio, port);
self.devices.insert(name.to_owned(), dev.clone());
self.device_metadata
.insert(&dev, Box::new(LpcUartMetadata::new(num, port, irq)));

if desc.num == SerialPortNumber::Com1 {
assert!(com1.is_none());
com1 = Some(dev);
Expand Down Expand Up @@ -1110,9 +1134,17 @@ impl MachineInitializer<'_> {
// NOTE: SoftNpu squats on com4.
let uart = LpcUart::new(chipset.irq_pin(ibmpc::IRQ_COM4).unwrap());
uart.set_autodiscard(true);
LpcUart::attach(&uart, &self.machine.bus_pio, ibmpc::PORT_COM4);
uart.attach(&self.machine.bus_pio, ibmpc::PORT_COM4);
self.devices
.insert(SpecKey::Name("softnpu-uart".to_string()), uart.clone());
self.device_metadata.insert(
&uart,
Box::new(LpcUartMetadata::new(
4,
ibmpc::PORT_COM4,
ibmpc::IRQ_COM4,
)),
);

// Start with no pipeline. The guest must load the initial P4 program.
let pipeline = Arc::new(std::sync::Mutex::new(None));
Expand Down Expand Up @@ -1433,14 +1465,52 @@ impl MachineInitializer<'_> {
Ok(Some(order.finish()))
}

fn generate_acpi_tables(
&self,
acpi_variant: acpi::AcpiVariant,
cpus: u8,
) -> Result<fwcfg::formats::AcpiTables, MachineInitError> {
let (lowmem, _) = get_spec_guest_ram_limits(self.spec);
let generators: Vec<_> = self
.devices
.values()
.filter_map(|dev| dev.as_dsdt_generator())
.collect();

// The values for pci_window_32 and pci_window_64 are set based on the
// original EDK2 ACPI tables, and currently don't exactly match the
// ranges defined in build_instance().
//
// Propolis doesn't verify if an MMIO operation happens in an address
// reserved for MMIO, so this doesn't cause problems for now, but the
// PCI windows should be updated to match what's reserved in
// build_instance().
let pci_window_32 = fwcfg::formats::PciWindow::new(
lowmem as u64,
PCI_MMIO32_END as u64,
)?;

let config = &fwcfg::formats::AcpiConfig {
acpi_variant,
num_cpus: cpus,
pci_window_32,
pci_window_64: fwcfg::formats::PciWindow::empty(),
dsdt_generators: &generators,
device_metadata: &self.device_metadata,
};
let acpi_tables = fwcfg::formats::AcpiTablesBuilder::new(config);
Ok(acpi_tables.build())
}

/// Initialize qemu `fw_cfg` device, and populate it with data including CPU
/// count, SMBIOS tables, and attached RAM-FB device.
/// count, SMBIOS and ACPI tables, and attached RAM-FB device.
///
/// Should not be called before [`Self::initialize_rom()`].
pub fn initialize_fwcfg(
&mut self,
cpus: u8,
bootrom_version: &Option<String>,
acpi_variant: acpi::AcpiVariant,
) -> Result<Arc<ramfb::RamFb>, MachineInitError> {
let fwcfg = fwcfg::FwCfg::new();
fwcfg
Expand Down Expand Up @@ -1479,6 +1549,19 @@ impl MachineInitializer<'_> {
.insert_named("etc/e820", e820_entry)
.map_err(|e| MachineInitError::FwcfgInsertFailed("e820", e))?;

let acpi_entries = self.generate_acpi_tables(acpi_variant, cpus)?;
fwcfg.insert_named("etc/acpi/tables", acpi_entries.tables).map_err(
|e| MachineInitError::FwcfgInsertFailed("acpi/tables", e),
)?;
fwcfg
.insert_named("etc/acpi/rsdp", acpi_entries.rsdp)
.map_err(|e| MachineInitError::FwcfgInsertFailed("acpi/rsdp", e))?;
fwcfg
.insert_named("etc/table-loader", acpi_entries.table_loader)
.map_err(|e| {
MachineInitError::FwcfgInsertFailed("table-loader", e)
})?;

let ramfb = ramfb::RamFb::create(
self.log.new(slog::o!("component" => "ramfb")),
);
Expand Down
2 changes: 2 additions & 0 deletions bin/propolis-server/src/lib/spec/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl SpecBuilder {
memory_mb: board.memory_mb,
chipset: board.chipset,
guest_hv_interface: board.guest_hv_interface,
acpi_variant: propolis::firmware::acpi::AcpiVariant::V0,
},
cpuid,
..Default::default()
Expand Down Expand Up @@ -403,6 +404,7 @@ mod test {
memory_mb: 512,
chipset: Chipset::I440Fx(I440Fx { enable_pcie: false }),
guest_hv_interface: GuestHypervisorInterface::Bhyve,
acpi_variant: propolis::firmware::acpi::AcpiVariant::V0,
};

SpecBuilder {
Expand Down
5 changes: 5 additions & 0 deletions bin/propolis-server/src/lib/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use thiserror::Error;
#[cfg(feature = "failure-injection")]
use propolis_api_types::instance_spec::components::devices::MigrationFailureInjector;

use propolis::firmware::acpi::AcpiVariant;
#[cfg(feature = "falcon")]
use propolis_api_types::instance_spec::components::{
backends::DlpiNetworkBackend,
Expand Down Expand Up @@ -160,6 +161,9 @@ pub(crate) struct Board {
pub memory_mb: u64,
pub chipset: Chipset,
pub guest_hv_interface: GuestHypervisorInterface,

// XXX: expose via the API once more variants are implemented.
pub acpi_variant: AcpiVariant,
}

impl Default for Board {
Expand All @@ -169,6 +173,7 @@ impl Default for Board {
memory_mb: 0,
chipset: Chipset::I440Fx(I440Fx { enable_pcie: false }),
guest_hv_interface: GuestHypervisorInterface::Bhyve,
acpi_variant: AcpiVariant::V0,
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions bin/propolis-server/src/lib/vm/ensure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ use std::sync::Arc;

use oximeter::types::ProducerRegistry;
use oximeter_instruments::kstat::KstatSampler;
use propolis::common::DeviceMetadataMap;
use propolis::enlightenment::{
bhyve::BhyveGuestInterface,
hyperv::{Features as HyperVFeatures, HyperV},
Expand Down Expand Up @@ -532,6 +533,7 @@ async fn initialize_vm_objects(
log: log.clone(),
machine: &machine,
devices: Default::default(),
device_metadata: DeviceMetadataMap::new(),
block_backends: Default::default(),
crucible_backends: Default::default(),
spec: &spec,
Expand Down Expand Up @@ -579,8 +581,11 @@ async fn initialize_vm_objects(
.initialize_storage_devices(&chipset, options.nexus_client.clone())
.await?;

let ramfb =
init.initialize_fwcfg(spec.board.cpus, &options.bootrom_version)?;
let ramfb = init.initialize_fwcfg(
spec.board.cpus,
&options.bootrom_version,
spec.board.acpi_variant,
)?;

// If we have a VM RoT, that RoT needs to be able to collect some
// information about the guest before it can be actually usable. It will do
Expand Down
1 change: 1 addition & 0 deletions bin/propolis-standalone/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ pbind.workspace = true
[features]
default = []
crucible = ["propolis/crucible-full", "propolis/oximeter", "crucible-client-types"]
acpi-debug = ["propolis/acpi-debug"]
7 changes: 7 additions & 0 deletions bin/propolis-standalone/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};

use cpuid_profile_config::*;
use propolis::block;
use propolis::firmware::acpi::AcpiVariant;
use propolis::hw::pci::Bdf;

use crate::cidata::build_cidata_be;
Expand Down Expand Up @@ -71,6 +72,12 @@ pub struct Main {

/// Request bootrom override boot order using the devices specified
pub boot_order: Option<Vec<String>>,

/// ACPI table variant to use for the VM
///
/// Default: V0
#[serde(default)]
pub acpi_variant: AcpiVariant,
}

#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
Expand Down
Loading
Loading