Skip to content

Commit 4eedc0e

Browse files
committed
Add PCIe _OSC method for OS capability negotiation
The OS calls _OSC on the PCIe host bridge to negotiate control of native PCIe features like hotplug, AER and PME. Without _OSC, Linux logs warning about missing capability negotiation(_OSC: platform retains control of PCIe features (AE_NOT_FOUND). Since as of now we don't have support for any PCIe handling, no capabilities are exposed. In future when PCIe handling is implemented the supported bits can be simply unmasked to expose them to the guest. Also to simplify the aml generation of _OSC itself introduce some high level wrappers around aml generation. [1]: https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/enabling-pci-express-native-control Signed-off-by: Amey Narkhede <ameynarkhede03@gmail.com>
1 parent d68d8c2 commit 4eedc0e

File tree

5 files changed

+183
-2
lines changed

5 files changed

+183
-2
lines changed

lib/propolis/src/firmware/acpi/aml.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use super::names::{encode_name_string, EisaId};
5+
use super::names::{encode_name_string, EisaId, UUID_SIZE};
66
use super::opcodes::*;
77

88
pub trait AmlWriter {
@@ -441,9 +441,86 @@ impl<'a> MethodGuard<'a> {
441441
encode_name_string(name, &mut self.builder.buf);
442442
}
443443

444+
pub fn return_arg(&mut self, n: u8) {
445+
assert!(n <= 6);
446+
self.builder.buf.push(RETURN_OP);
447+
self.builder.buf.push(ARG0_OP + n);
448+
}
449+
444450
pub fn raw(&mut self, bytes: &[u8]) {
445451
self.builder.raw(bytes);
446452
}
453+
454+
pub fn create_dword_field(&mut self, source: u8, offset: u8, name: &str) {
455+
self.builder.buf.push(CREATE_DWORD_FIELD_OP);
456+
self.builder.buf.push(source);
457+
if offset == 0 {
458+
self.builder.buf.push(ZERO_OP);
459+
} else {
460+
self.builder.buf.push(BYTE_PREFIX);
461+
self.builder.buf.push(offset);
462+
}
463+
encode_name_string(name, &mut self.builder.buf);
464+
}
465+
466+
pub fn if_uuid_equal(
467+
&mut self,
468+
uuid: &[u8; 16],
469+
body: impl FnOnce(&mut Self),
470+
) {
471+
self.builder.buf.push(IF_OP);
472+
let start = self.builder.buf.len();
473+
self.builder.buf.extend_from_slice(&[0; MAX_PKG_LENGTH_BYTES]);
474+
475+
self.builder.buf.push(LEQUAL_OP);
476+
self.builder.buf.push(ARG0_OP);
477+
478+
self.builder.buf.push(BUFFER_OP);
479+
let buffer_start = self.builder.buf.len();
480+
self.builder.buf.extend_from_slice(&[0; MAX_PKG_LENGTH_BYTES]);
481+
self.builder.buf.push(BYTE_PREFIX);
482+
self.builder.buf.push(UUID_SIZE as u8);
483+
let buffer_content_start = self.builder.buf.len();
484+
self.builder.buf.extend_from_slice(uuid);
485+
finalize_pkg_length(
486+
&mut self.builder.buf,
487+
buffer_start,
488+
buffer_content_start,
489+
);
490+
491+
let content_start = self.builder.buf.len();
492+
body(self);
493+
finalize_pkg_length(&mut self.builder.buf, start, content_start);
494+
}
495+
496+
pub fn else_block(&mut self, body: impl FnOnce(&mut Self)) {
497+
self.builder.buf.push(ELSE_OP);
498+
let start = self.builder.buf.len();
499+
self.builder.buf.extend_from_slice(&[0; MAX_PKG_LENGTH_BYTES]);
500+
let content_start = self.builder.buf.len();
501+
body(self);
502+
finalize_pkg_length(&mut self.builder.buf, start, content_start);
503+
}
504+
505+
pub fn and_to(&mut self, name: &str, mask: u32) {
506+
self.builder.buf.push(AND_OP);
507+
encode_name_string(name, &mut self.builder.buf);
508+
mask.write_aml(&mut self.builder.buf);
509+
encode_name_string(name, &mut self.builder.buf);
510+
}
511+
512+
pub fn or_to(&mut self, name: &str, value: u32) {
513+
self.builder.buf.push(OR_OP);
514+
encode_name_string(name, &mut self.builder.buf);
515+
value.write_aml(&mut self.builder.buf);
516+
encode_name_string(name, &mut self.builder.buf);
517+
}
518+
519+
pub fn store(&mut self, source: &str, dest: &str) {
520+
self.builder.buf.push(STORE_OP);
521+
encode_name_string(source, &mut self.builder.buf);
522+
encode_name_string(dest, &mut self.builder.buf);
523+
}
447524
}
448525

449526
impl Drop for MethodGuard<'_> {

lib/propolis/src/firmware/acpi/dsdt.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,46 @@ fn build_pcie_host_bridge(
189189
}
190190
}
191191
pci0.name_package("_PRT", &prt_entries);
192+
pci0.name("SUPP", &0u32);
193+
194+
build_pcie_osc_method(&mut pci0);
195+
}
196+
197+
fn build_pcie_osc_method(dev: &mut super::aml::DeviceGuard<'_>) {
198+
use super::names::{encode_uuid, UUID_SIZE};
199+
use super::opcodes::*;
200+
201+
const PCIE_UUID: [u8; UUID_SIZE] =
202+
encode_uuid("33DB4D5B-1FF7-401C-9657-7441C03DD766");
203+
const OSC_STATUS_UNSUPPORT_UUID: u32 = 1 << 2;
204+
const OSC_CTRL_PCIE_HP: u32 = 1 << 0;
205+
const OSC_CTRL_SHPC_HP: u32 = 1 << 1;
206+
const OSC_CTRL_PCIE_PME: u32 = 1 << 2;
207+
const OSC_CTRL_PCIE_AER: u32 = 1 << 3;
208+
const OSC_CTRL_PCIE_CAP: u32 = 1 << 4;
209+
210+
let unsupported_mask = !(OSC_CTRL_PCIE_HP
211+
| OSC_CTRL_SHPC_HP
212+
| OSC_CTRL_PCIE_PME
213+
| OSC_CTRL_PCIE_AER
214+
| OSC_CTRL_PCIE_CAP);
215+
216+
let mut osc = dev.method("_OSC", 4, false);
217+
218+
osc.create_dword_field(ARG3_OP, 0, "CDW1");
219+
osc.create_dword_field(ARG3_OP, 4, "CDW2");
220+
osc.create_dword_field(ARG3_OP, 8, "CDW3");
221+
222+
osc.if_uuid_equal(&PCIE_UUID, |osc| {
223+
osc.store("CDW2", "SUPP");
224+
osc.and_to("CDW3", unsupported_mask);
225+
});
226+
227+
osc.else_block(|osc| {
228+
osc.or_to("CDW1", OSC_STATUS_UNSUPPORT_UUID);
229+
});
230+
231+
osc.return_arg(3);
192232
}
193233

194234
/// Build a PNP0C02 motherboard resources device to reserve ECAM space.

lib/propolis/src/firmware/acpi/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ pub mod tables;
1313

1414
pub use aml::{AmlBuilder, AmlWriter, DeviceGuard, MethodGuard, ScopeGuard};
1515
pub use dsdt::{build_dsdt_aml, DsdtConfig, DsdtGenerator, DsdtScope, PcieConfig};
16-
pub use names::EisaId;
16+
pub use names::{encode_uuid, EisaId};
1717
pub use resources::ResourceTemplateBuilder;
1818
pub use tables::{Dsdt, Facs, Fadt, Hpet, Madt, Mcfg, Rsdt, Rsdp, Xsdt};

lib/propolis/src/firmware/acpi/names.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,57 @@ impl EisaId {
133133
}
134134
}
135135

136+
/// UUID byte size.
137+
pub const UUID_SIZE: usize = 16;
138+
139+
/// Encode a UUID string into ACPI ToUUID format at compile time.
140+
///
141+
/// UUID format: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
142+
///
143+
/// ACPI ToUUID uses mixed-endian encoding:
144+
/// - First 3 groups (8-4-4 hex digits): little-endian
145+
/// - Last 2 groups (4-12 hex digits): big-endian
146+
pub const fn encode_uuid(uuid: &str) -> [u8; UUID_SIZE] {
147+
let b = uuid.as_bytes();
148+
assert!(b.len() == 36, "UUID must be 36 characters");
149+
assert!(
150+
b[8] == b'-' && b[13] == b'-' && b[18] == b'-' && b[23] == b'-',
151+
"UUID must have dashes at positions 8, 13, 18, 23"
152+
);
153+
154+
const fn hex(c: u8) -> u8 {
155+
match c {
156+
b'0'..=b'9' => c - b'0',
157+
b'A'..=b'F' => c - b'A' + 10,
158+
b'a'..=b'f' => c - b'a' + 10,
159+
_ => panic!("invalid hex"),
160+
}
161+
}
162+
163+
const fn byte(b: &[u8], i: usize) -> u8 {
164+
(hex(b[i]) << 4) | hex(b[i + 1])
165+
}
166+
167+
[
168+
byte(b, 6),
169+
byte(b, 4),
170+
byte(b, 2),
171+
byte(b, 0),
172+
byte(b, 11),
173+
byte(b, 9),
174+
byte(b, 16),
175+
byte(b, 14),
176+
byte(b, 19),
177+
byte(b, 21),
178+
byte(b, 24),
179+
byte(b, 26),
180+
byte(b, 28),
181+
byte(b, 30),
182+
byte(b, 32),
183+
byte(b, 34),
184+
]
185+
}
186+
136187
#[cfg(test)]
137188
mod tests {
138189
use super::*;
@@ -190,4 +241,16 @@ mod tests {
190241
fn eisaid_rejects_lowercase() {
191242
encode_eisaid("pnp0A08");
192243
}
244+
245+
#[test]
246+
fn uuid_encoding() {
247+
let uuid = encode_uuid("33DB4D5B-1FF7-401C-9657-7441C03DD766");
248+
assert_eq!(
249+
uuid,
250+
[
251+
0x5B, 0x4D, 0xDB, 0x33, 0xF7, 0x1F, 0x1C, 0x40, 0x96, 0x57,
252+
0x74, 0x41, 0xC0, 0x3D, 0xD7, 0x66,
253+
]
254+
);
255+
}
193256
}

lib/propolis/src/firmware/acpi/opcodes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub const SIZEOF_OP: u8 = 0x87;
9494
pub const INDEX_OP: u8 = 0x88;
9595
pub const DEREF_OF_OP: u8 = 0x83;
9696
pub const REF_OF_OP: u8 = 0x71;
97+
pub const CREATE_DWORD_FIELD_OP: u8 = 0x8A;
9798

9899
// Resource template end tag
99100
pub const END_TAG: u8 = 0x79;

0 commit comments

Comments
 (0)