Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1b0f524
Add fw_cfg table-loader helpers for ACPI table generation
glitzflitz Dec 29, 2025
5b46344
Add RSDT, XSDT and RSDP tables
glitzflitz Dec 29, 2025
d9f8ac4
Add FADT and DSDT table generation
glitzflitz Dec 29, 2025
2ea6aaa
Add MADT table
glitzflitz Dec 29, 2025
17a2236
Add MCFG and HPET tables
glitzflitz Dec 29, 2025
1aac6c6
Add FACS table
glitzflitz Dec 29, 2025
99a2d12
Define AML opcode constants
glitzflitz Dec 29, 2025
0ff3a6d
Add ACPI name encoding utilities
glitzflitz Dec 29, 2025
50ee02c
Introduce AML bytecode generation
glitzflitz Dec 29, 2025
4aa74fd
Support resource construction for ACPI methods
glitzflitz Dec 29, 2025
317d357
Wire up firmware/acpi module exports
glitzflitz Dec 29, 2025
0a0b3f2
Generate DSDT with PCIe host bridge
glitzflitz Dec 30, 2025
b54f0e1
Implement DsdtGenerator for LpcUart
glitzflitz Jan 4, 2026
37e7b0f
Add PS/2 controller in DSDT
glitzflitz Dec 30, 2025
d68d8c2
Add Qemu pvpanic device to DSDT
glitzflitz Jan 4, 2026
4eedc0e
Add PCIe _OSC method for OS capability negotiation
glitzflitz Dec 30, 2025
7036a3e
Prepare the ACPI tables for generation
glitzflitz Jan 4, 2026
61f0ed4
Wire up ACPI table generation via fw_cfg
glitzflitz Dec 31, 2025
ac472cf
acpi: generate ACPI tables using acpi_tables crate
lgfa29 Mar 16, 2026
13b8439
docs: minor docs touch-ups
lgfa29 Apr 20, 2026
fee46d5
fix clippy
lgfa29 Apr 20, 2026
a6b3ae3
minor fixes
lgfa29 Apr 21, 2026
b7132f2
tests: add phd-test for ACPI tables
lgfa29 Apr 21, 2026
5104fb5
Merge remote-tracking branch 'origin/master' into acpi_fwcfg_reord
lgfa29 Apr 21, 2026
5fed2fc
fix clippy
lgfa29 Apr 21, 2026
e9f7fb1
fwcg: handle invalid configuration
lgfa29 Apr 22, 2026
d2f8d37
minor fixes and more tests
lgfa29 Apr 22, 2026
e9204ea
minor fixes and improvements
lgfa29 Apr 23, 2026
bb6f678
acpi: add more shared constants b/w fadt and dsdt
lgfa29 Apr 30, 2026
0e1447a
acpi: add test for ACPI table generation
lgfa29 May 21, 2026
76ff270
acpi: use stable references to ACPI names
lgfa29 May 20, 2026
a72a717
acpi: update comments in DSDT table generation
lgfa29 May 22, 2026
5ba9e22
acpi: document bhyve and unhandled IO ports
lgfa29 May 22, 2026
889b95a
acpi: expand documentation for sleep states
lgfa29 May 22, 2026
2e3e048
acpi: wrapper for IO port declaration
lgfa29 May 22, 2026
48a2dc1
acpi: add historical note about ACPI tables
lgfa29 May 22, 2026
a5d716f
acpi: reuse existing IO port values
lgfa29 May 22, 2026
71c8e6f
acpi: use SERIALIZED and NOT_SERIALIZED constants
lgfa29 May 22, 2026
9f29190
acpi: expand on MMIO32 and MMIO64 range values
lgfa29 May 23, 2026
02bf8cd
acpi: update lpc.rs
lgfa29 May 25, 2026
b82be28
acpi: update fadt.rs
lgfa29 May 26, 2026
1e97c1c
acpi: expand doc on ACPI reset register
lgfa29 May 26, 2026
b70a2c8
acpi: add note about high vCPU count
lgfa29 May 26, 2026
99bf8c0
acpi: reuse more existing values and improve docs
lgfa29 May 26, 2026
75fd262
acpi: update fwcfg.rs
lgfa29 May 26, 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 = "1d3
vm-attest = { git = "https://github.com/oxidecomputer/vm-attest", rev = "2cdd17580a4fc6c871d24797016af8dbaac9421d", 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' }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as long as we're pulling this in as a git dependency, lets include a rev = ... so it's clear which rev we're actually using without fishing through Cargo.toml please

(do you have any remaining not-upstreamed changes? :D or is there not a sufficiently new release yet?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I meant to create a tag once the changes stabilized.

Most of my upstream PRs are still open (https://github.com/rust-vmm/acpi_tables/pulls/lgfa29), but we will need to keep running a fork until we update our ACPI table versions (https://github.com/oxidecomputer/acpi_tables/blob/main-oxide/src/fadt_3.rs).

I will create the tag and update the reference here.


#
# 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
86 changes: 76 additions & 10 deletions bin/propolis-server/src/lib/initializer.rs
Original file line number Diff line number Diff line change
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,12 @@ pub enum MachineInitError {
/// Arbitrary ROM limit for now
const MAX_ROM_SIZE: usize = 0x20_0000;

/// End address of the 32-bit PCI MMIO window.
// XXX(acpi): Value inherited from the original EDK2 static tables. It should
// match the actual memory regions registered in the instance.
// https://github.com/oxidecomputer/edk2/blob/f33871f488bfbbc080e0f7e3881e04d0db0b6367/OvmfPkg/PlatformPei/Platform.c#L180-L192
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 @@ -406,16 +415,25 @@ 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 (uart_name, irq, port) = match desc.num {
SerialPortNumber::Com1 => {
("COM1", ibmpc::IRQ_COM1, ibmpc::PORT_COM1)
}
SerialPortNumber::Com2 => {
("COM2", ibmpc::IRQ_COM2, ibmpc::PORT_COM2)
}
SerialPortNumber::Com3 => {
("COM3", ibmpc::IRQ_COM3, ibmpc::PORT_COM3)
}
SerialPortNumber::Com4 => {
("COM4", ibmpc::IRQ_COM4, ibmpc::PORT_COM4)
}
};

let dev = LpcUart::new(chipset.irq_pin(irq).unwrap());
let dev =
LpcUart::new(uart_name, irq, chipset.irq_pin(irq).unwrap());
dev.set_autodiscard(true);
LpcUart::attach(&dev, &self.machine.bus_pio, port);
dev.attach(&self.machine.bus_pio, port);
self.devices.insert(name.to_owned(), dev.clone());
if desc.num == SerialPortNumber::Com1 {
assert!(com1.is_none());
Expand Down Expand Up @@ -1091,9 +1109,13 @@ impl MachineInitializer<'_> {
// Set up an LPC uart for ASIC management comms from the guest.
//
// NOTE: SoftNpu squats on com4.
let uart = LpcUart::new(chipset.irq_pin(ibmpc::IRQ_COM4).unwrap());
let uart = LpcUart::new(
"COM4",
ibmpc::IRQ_COM4,
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());

Expand Down Expand Up @@ -1416,8 +1438,39 @@ impl MachineInitializer<'_> {
Ok(Some(order.finish()))
}

fn generate_acpi_tables(
&self,
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();

let pci_window_32 = fwcfg::formats::PciWindow::new(
lowmem as u64,
PCI_MMIO32_END as u64,
)?;

let config = &fwcfg::formats::AcpiConfig {
num_cpus: cpus,
pci_window_32,
// XXX(acpi): Value inherited from the original EDK2 static tables,
// where the 64-bit PCI MMIO region was never set. It
// should match the actual memory regions registered in
// the instance.
// https://github.com/oxidecomputer/edk2/blob/f33871f488bfbbc080e0f7e3881e04d0db0b6367/OvmfPkg/AcpiPlatformDxe/Qemu.c#L284-L286
pci_window_64: fwcfg::formats::PciWindow::empty(),
dsdt_generators: &generators,
};
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(
Expand Down Expand Up @@ -1462,6 +1515,19 @@ impl MachineInitializer<'_> {
.insert_named("etc/e820", e820_entry)
.map_err(|e| MachineInitError::FwcfgInsertFailed("e820", e))?;

let acpi_entries = self.generate_acpi_tables(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
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"]
81 changes: 73 additions & 8 deletions bin/propolis-standalone/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const PAGE_OFFSET: u64 = 0xfff;
// Arbitrary ROM limit for now
const MAX_ROM_SIZE: usize = 0x20_0000;

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

const MIN_RT_THREADS: usize = 8;
const BASE_RT_THREADS: usize = 4;

Expand Down Expand Up @@ -1071,6 +1077,37 @@ fn generate_bootorder(
Ok(Some(order.finish()))
}

fn generate_acpi_tables(
cpus: u8,
lowmem: usize,
inventory: &Inventory,
) -> anyhow::Result<fwcfg::formats::AcpiTables> {
let generators: Vec<_> = inventory
.devs
.values()
.filter_map(|dev| dev.as_dsdt_generator())
.collect();

let pci_window_32 =
fwcfg::formats::PciWindow::new(lowmem as u64, PCI_MMIO32_END as u64)
.context("invalid PCI window range")?;

let config = &fwcfg::formats::AcpiConfig {
num_cpus: cpus,
pci_window_32,
// XXX(acpi): Value inherited from the original EDK2 static tables,
// where the 64-bit PCI MMIO region was never set. It
// should match the actual memory regions registered in
// the instance.
// https://github.com/oxidecomputer/edk2/blob/f33871f488bfbbc080e0f7e3881e04d0db0b6367/OvmfPkg/AcpiPlatformDxe/Qemu.c#L284-L286
pci_window_64: fwcfg::formats::PciWindow::empty(),
dsdt_generators: &generators,
};
let acpi_tables = fwcfg::formats::AcpiTablesBuilder::new(config);

Ok(acpi_tables.build())
}

fn setup_instance(
config: config::Config,
from_restore: bool,
Expand Down Expand Up @@ -1183,10 +1220,26 @@ fn setup_instance(
guard.inventory.register(&hpet);

// UARTs
let com1 = LpcUart::new(chipset_lpc.irq_pin(ibmpc::IRQ_COM1).unwrap());
let com2 = LpcUart::new(chipset_lpc.irq_pin(ibmpc::IRQ_COM2).unwrap());
let com3 = LpcUart::new(chipset_lpc.irq_pin(ibmpc::IRQ_COM3).unwrap());
let com4 = LpcUart::new(chipset_lpc.irq_pin(ibmpc::IRQ_COM4).unwrap());
let com1 = LpcUart::new(
"COM1",
ibmpc::IRQ_COM1,
chipset_lpc.irq_pin(ibmpc::IRQ_COM1).unwrap(),
);
let com2 = LpcUart::new(
"COM2",
ibmpc::IRQ_COM2,
chipset_lpc.irq_pin(ibmpc::IRQ_COM2).unwrap(),
);
let com3 = LpcUart::new(
"COM3",
ibmpc::IRQ_COM3,
chipset_lpc.irq_pin(ibmpc::IRQ_COM3).unwrap(),
);
let com4 = LpcUart::new(
"COM4",
ibmpc::IRQ_COM4,
chipset_lpc.irq_pin(ibmpc::IRQ_COM4).unwrap(),
);

com1_sock.spawn(
Arc::clone(&com1) as Arc<dyn Sink>,
Expand All @@ -1200,10 +1253,10 @@ fn setup_instance(
com4.set_autodiscard(true);

let pio = &machine.bus_pio;
LpcUart::attach(&com1, pio, ibmpc::PORT_COM1);
LpcUart::attach(&com2, pio, ibmpc::PORT_COM2);
LpcUart::attach(&com3, pio, ibmpc::PORT_COM3);
LpcUart::attach(&com4, pio, ibmpc::PORT_COM4);
com1.attach(pio, ibmpc::PORT_COM1);
com2.attach(pio, ibmpc::PORT_COM2);
com3.attach(pio, ibmpc::PORT_COM3);
com4.attach(pio, ibmpc::PORT_COM4);
guard.inventory.register_instance(&com1, "com1");
guard.inventory.register_instance(&com2, "com2");
guard.inventory.register_instance(&com3, "com3");
Expand Down Expand Up @@ -1425,6 +1478,18 @@ fn setup_instance(
let e820_entry = generate_e820(machine, log).expect("can build E820 table");
fwcfg.insert_named("etc/e820", e820_entry).unwrap();

let acpi_entries = generate_acpi_tables(cpus, lowmem, &guard.inventory)
.expect("can build ACPI tables");
fwcfg
.insert_named("etc/acpi/tables", acpi_entries.tables)
.context("Failed to insert ACPI tables")?;
fwcfg
.insert_named("etc/acpi/rsdp", acpi_entries.rsdp)
.context("Failed to insert ACPI RSDP")?;
fwcfg
.insert_named("etc/table-loader", acpi_entries.table_loader)
.context("Failed to insert ACPI table-loader")?;

fwcfg.attach(pio, &machine.acc_mem);

guard.inventory.register(&fwcfg);
Expand Down
2 changes: 2 additions & 0 deletions lib/propolis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ tokio = { workspace = true, features = ["full"] }
futures.workspace = true
paste.workspace = true
pin-project-lite.workspace = true
acpi_tables.workspace = true
anyhow.workspace = true
rgb_frame.workspace = true
rfb.workspace = true
Expand Down Expand Up @@ -65,6 +66,7 @@ rand.workspace = true
default = []
crucible-full = ["crucible", "crucible-client-types", "oximeter", "nexus-client"]
falcon = ["libloading", "p9ds", "dlpi", "ispf", "rand", "softnpu", "viona_api/falcon"]
acpi-debug = []

# TODO until crucible#1280 is addressed, enabling Nexus notifications is done
# through a feature flag.
Expand Down
Loading