Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
108 changes: 91 additions & 17 deletions lib/propolis/src/firmware/acpi/dsdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,30 @@ pub enum DsdtScope {
Lpc, // \_SB.PCI0.LPC scope.
}

/// The types of devices represented in specific parts of the DSDT.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DsdtDeviceType {
PS2Ctrl,
Uart,
}

/// An implementer of DsdtGenerator is able to generate AML code to be loaded
/// into the DSDT ACPI table.
pub trait DsdtGenerator {
/// Returns the scope of the DSDT table in which the generated AML code
/// should be placed.
fn dsdt_scope(&self) -> DsdtScope;

/// Returns the device type this generator represents.
///
/// This value can be used to place the generated code into specific
/// positions in the DSDT.
///
/// If `None`, the generated code is not guaranteed have a stable position.
fn device_type(&self) -> Option<DsdtDeviceType> {
None
}

fn to_aml_bytes(
&self,
acpi_variant: AcpiVariant,
Expand All @@ -69,14 +86,18 @@ pub trait DsdtGenerator {
/// where a `&dyn Aml` is needed.
struct DsdtGeneratorAml<'a> {
scope: DsdtScope,
device_type: Option<DsdtDeviceType>,
config: &'a DsdtConfig<'a>,
}
impl<'a> Aml for DsdtGeneratorAml<'a> {
fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
self.config
.generators
.iter()
.filter(|&&g| g.dsdt_scope() == self.scope)
.filter(|&&g| {
g.dsdt_scope() == self.scope
&& g.device_type() == self.device_type
})
Comment on lines +113 to +116

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.

so.. I'm realizing one wrinkle we'll still have is that we're at the whim of SpecKey ordering for a given device_type. that is, if something sends propolis-server an InstanceSpec which has four serial ports named com{1,2,3,4}, then changes how it adds serial ports such that they're named com{4,3,2,1} but name ports in ascending order, we'll dutifully reverse the order of those ports' AML.

we can probably demand that items implement Ord so we can sort the filtered generators? but this is all pretty niche compared to the current issue you're fixing. and it's not like we have anything else that's present multiple times in the ACPI tables. so as emphatically as I can say: I think we should land this with an issue and note about the remaining SpecKey sensitivity

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hum...right. Although the generators are sorted by SpecKey, so you would need to have a weird combination where the serial port key didn't quite match the port number. Weird, but still possible 😄

I added a note about it in a2f33de and I will open a follow-up issue.

.for_each(|&g| {
g.to_aml_bytes(
self.config.acpi_variant,
Expand Down Expand Up @@ -155,6 +176,7 @@ impl<'a> Aml for Dsdt<'a> {
&PciRootBridge { config: &self.config },
&DsdtGeneratorAml {
scope: DsdtScope::SystemBus,
device_type: None,

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.

the main thought i've got here is: while i think this gives Propolis good control over how to lay out items in the tables (and so, broadly, i'm a fan!) if we have a device on SystemBus and forget to add a stanza to emit the AML here, it'll probably be really confusing and frustrating to debug.

i'm imagining that maybe we could have a bit on devices to report if we've printed their AML, though to_aml_bytes takes &self, so we'd either change that or otherwise do interior mutability. then finally we could have an impl Drop for DsdtConfig that checks our work (and warns or panics or something if there are devices we've forgotten about).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, that's a good point. Is 8d38895 more or less what you had in mind?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Looking at this with fresh eyes today, I think it makes more sense to keep state in Dsdt rather DsdtConfig. That way DsdtConfig is just a container for configuration values. I moved the generator state in 42acb24

config: &self.config,
},
],
Expand Down Expand Up @@ -187,6 +209,7 @@ impl<'a> Aml for PciRootBridge<'a> {
&PciRootBridgeLpc { config: self.config },
&DsdtGeneratorAml {
scope: DsdtScope::PciRoot,
device_type: None,
config: self.config,
},
],
Expand Down Expand Up @@ -805,6 +828,12 @@ impl<'a> Aml for PciRootBridgeLpc<'a> {
),
&DsdtGeneratorAml {
scope: DsdtScope::Lpc,
device_type: Some(DsdtDeviceType::PS2Ctrl),
config: self.config,
},
&DsdtGeneratorAml {
scope: DsdtScope::Lpc,
device_type: Some(DsdtDeviceType::Uart),
config: self.config,
},
// QEMU panic device.
Expand Down Expand Up @@ -859,6 +888,11 @@ impl<'a> Aml for PciRootBridgeLpc<'a> {
),
],
),
&DsdtGeneratorAml {
scope: DsdtScope::Lpc,
device_type: None,
config: self.config,
},
],
)
.to_aml_bytes(sink);
Expand Down Expand Up @@ -1043,12 +1077,17 @@ mod test {

struct MockDsdtGenerator {
scope: DsdtScope,
device_type: Option<DsdtDeviceType>,
}
impl DsdtGenerator for MockDsdtGenerator {
fn dsdt_scope(&self) -> DsdtScope {
self.scope
}

fn device_type(&self) -> Option<DsdtDeviceType> {
self.device_type
}

fn to_aml_bytes(
&self,
acpi_variant: AcpiVariant,
Expand Down Expand Up @@ -1089,11 +1128,20 @@ mod test {

#[test]
fn dsdt_generator_aml() {
let sb = Arc::new(MockDsdtGenerator { scope: DsdtScope::SystemBus });
let pci = Arc::new(MockDsdtGenerator { scope: DsdtScope::PciRoot });
let lpc = Arc::new(MockDsdtGenerator { scope: DsdtScope::Lpc });

let generators: Vec<&dyn DsdtGenerator> = vec![&*sb, &*pci, &*lpc];
let sb = Arc::new(MockDsdtGenerator {
scope: DsdtScope::SystemBus,
device_type: None,
});
let pci = Arc::new(MockDsdtGenerator {
scope: DsdtScope::PciRoot,
device_type: None,
});
let lpc_ps2 = Arc::new(MockDsdtGenerator {
scope: DsdtScope::Lpc,
device_type: Some(DsdtDeviceType::PS2Ctrl),
});

let generators: Vec<&dyn DsdtGenerator> = vec![&*sb, &*pci, &*lpc_ps2];

let mut got = Vec::new();
let mut expected = Vec::new();
Expand All @@ -1104,7 +1152,7 @@ mod test {
device_metadata
.insert(&pci, Box::new(MockDsdtGeneratorMetadata { data: 2 }));
device_metadata
.insert(&lpc, Box::new(MockDsdtGeneratorMetadata { data: 3 }));
.insert(&lpc_ps2, Box::new(MockDsdtGeneratorMetadata { data: 3 }));

let config = DsdtConfig {
acpi_variant: AcpiVariant::V0,
Expand All @@ -1113,8 +1161,12 @@ mod test {
};

// Filter by SystemBus.
DsdtGeneratorAml { scope: DsdtScope::SystemBus, config: &config }
.to_aml_bytes(&mut got);
DsdtGeneratorAml {
scope: DsdtScope::SystemBus,
device_type: None,
config: &config,
}
.to_aml_bytes(&mut got);

aml::ResourceTemplate::new(vec![
&aml::Name::new("SCOP".into(), &"SystemBus"),
Expand All @@ -1129,8 +1181,12 @@ mod test {
got.clear();
expected.clear();

DsdtGeneratorAml { scope: DsdtScope::PciRoot, config: &config }
.to_aml_bytes(&mut got);
DsdtGeneratorAml {
scope: DsdtScope::PciRoot,
device_type: None,
config: &config,
}
.to_aml_bytes(&mut got);

aml::ResourceTemplate::new(vec![
&aml::Name::new("SCOP".into(), &"PciRoot"),
Expand All @@ -1141,12 +1197,24 @@ mod test {

assert_eq!(expected, got);

// Filter by Lpc.
// Filter by Lpc scope and device type.
got.clear();
expected.clear();

DsdtGeneratorAml { scope: DsdtScope::Lpc, config: &config }
.to_aml_bytes(&mut got);
DsdtGeneratorAml {
scope: DsdtScope::Lpc,
device_type: Some(DsdtDeviceType::PS2Ctrl),
config: &config,
}
.to_aml_bytes(&mut got);

// Filter on device type without generators.
DsdtGeneratorAml {
scope: DsdtScope::Lpc,
device_type: Some(DsdtDeviceType::Uart),
config: &config,
}
.to_aml_bytes(&mut got);

aml::ResourceTemplate::new(vec![
&aml::Name::new("SCOP".into(), &"Lpc"),
Expand All @@ -1163,9 +1231,15 @@ mod test {
let config = DsdtConfig {
acpi_variant: AcpiVariant::V0,
generators: &[
&MockDsdtGenerator { scope: DsdtScope::SystemBus },
&MockDsdtGenerator { scope: DsdtScope::PciRoot },
&MockDsdtGenerator { scope: DsdtScope::Lpc },
&MockDsdtGenerator {
scope: DsdtScope::SystemBus,
device_type: None,
},
&MockDsdtGenerator {
scope: DsdtScope::PciRoot,
device_type: None,
},
&MockDsdtGenerator { scope: DsdtScope::Lpc, device_type: None },
],
device_metadata: &DeviceMetadataMap::new(),
};
Expand Down
2 changes: 1 addition & 1 deletion lib/propolis/src/firmware/acpi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub mod rsdp;
pub mod ssdt_edk2;
pub mod xsdt;

pub use dsdt::{Dsdt, DsdtConfig, DsdtGenerator, DsdtScope};
pub use dsdt::{Dsdt, DsdtConfig, DsdtDeviceType, DsdtGenerator, DsdtScope};
pub use facs::{Facs, FacsConfig};
pub use fadt::{
Fadt, FadtConfig, FADT_DSDT_LEN, FADT_DSDT_OFFSET, FADT_FACS_LEN,
Expand Down
4 changes: 4 additions & 0 deletions lib/propolis/src/hw/ps2/ctrl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,10 @@ impl acpi::DsdtGenerator for PS2Ctrl {
acpi::DsdtScope::Lpc
}

fn device_type(&self) -> Option<acpi::DsdtDeviceType> {
Some(acpi::DsdtDeviceType::PS2Ctrl)
}

fn to_aml_bytes(
&self,
_: acpi::AcpiVariant,
Expand Down
4 changes: 4 additions & 0 deletions lib/propolis/src/hw/uart/lpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ impl acpi::DsdtGenerator for LpcUart {
acpi::DsdtScope::Lpc
}

fn device_type(&self) -> Option<acpi::DsdtDeviceType> {
Some(acpi::DsdtDeviceType::Uart)
}

// This AML code is inherited from the original EDK2 static tables.
fn to_aml_bytes(
&self,
Expand Down
Binary file modified phd-tests/tests/testdata/acpi/v0/dsdt.dat
Binary file not shown.
Loading