Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 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
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
10 changes: 10 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' }

#
# It's common during development to use a local copy of various complex
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 (irq, port, uart_name) = match desc.num {
SerialPortNumber::Com1 => {
(ibmpc::IRQ_COM1, ibmpc::PORT_COM1, "COM1")
}
SerialPortNumber::Com2 => {
(ibmpc::IRQ_COM2, ibmpc::PORT_COM2, "COM2")
}
SerialPortNumber::Com3 => {
(ibmpc::IRQ_COM3, ibmpc::PORT_COM3, "COM3")
}
SerialPortNumber::Com4 => {
(ibmpc::IRQ_COM4, ibmpc::PORT_COM4, "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
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("failed to 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
1 change: 1 addition & 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
Loading
Loading