acpi/
ssdt.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4pub use crate::aml::*;
5use memory_range::MemoryRange;
6use zerocopy::FromBytes;
7use zerocopy::Immutable;
8use zerocopy::IntoBytes;
9use zerocopy::KnownLayout;
10
11#[repr(C, packed)]
12#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
13pub struct DescriptionHeader {
14    pub signature: u32,
15    _length: u32, // placeholder, filled in during serialization to bytes
16    pub revision: u8,
17    _checksum: u8, // placeholder, filled in during serialization to bytes
18    pub oem_id: [u8; 6],
19    pub oem_table_id: u64,
20    pub oem_revision: u32,
21    pub creator_id: u32,
22    pub creator_rev: u32,
23}
24
25fn encode_pcie_name(mut pcie_index: u32) -> Vec<u8> {
26    assert!(pcie_index < 1000);
27    let mut temp = "PCI0".as_bytes().to_vec();
28    let mut i = temp.len() - 1;
29    while pcie_index > 0 {
30        temp[i] = b'0' + (pcie_index % 10) as u8;
31        pcie_index /= 10;
32        i -= 1;
33    }
34    temp
35}
36
37pub struct Ssdt {
38    description_header: DescriptionHeader,
39    objects: Vec<u8>,
40    pcie_ecam_ranges: Vec<MemoryRange>,
41}
42
43impl Ssdt {
44    pub fn new() -> Self {
45        Self {
46            description_header: DescriptionHeader {
47                signature: u32::from_le_bytes(*b"SSDT"),
48                _length: 0,
49                revision: 2,
50                _checksum: 0,
51                oem_id: *b"MSFTVM",
52                oem_table_id: 0x313054445353, // b'SSDT01'
53                oem_revision: 1,
54                creator_id: u32::from_le_bytes(*b"MSFT"),
55                creator_rev: 0x01000000,
56            },
57            objects: vec![],
58            pcie_ecam_ranges: vec![],
59        }
60    }
61
62    pub fn to_bytes(&self) -> Vec<u8> {
63        let mut byte_stream = Vec::new();
64        byte_stream.extend_from_slice(self.description_header.as_bytes());
65        byte_stream.extend_from_slice(&self.objects);
66
67        // N.B. Certain guest OSes will only probe ECAM ranges if they are
68        // reserved in the resources of an ACPI motherboard device.
69        if !self.pcie_ecam_ranges.is_empty() {
70            let mut vmod = Device::new(b"VMOD");
71            vmod.add_object(&NamedObject::new(b"_HID", &EisaId(*b"PNP0C02")));
72
73            let mut crs = CurrentResourceSettings::new();
74            for ecam_range in &self.pcie_ecam_ranges {
75                crs.add_resource(&QwordMemory::new(
76                    ecam_range.start(),
77                    ecam_range.end() - ecam_range.start(),
78                ));
79            }
80            vmod.add_object(&crs);
81            vmod.append_to_vec(&mut byte_stream);
82        }
83
84        let length = byte_stream.len();
85        byte_stream[4..8].copy_from_slice(&u32::try_from(length).unwrap().to_le_bytes());
86        let mut checksum: u8 = 0;
87        for byte in &byte_stream {
88            checksum = checksum.wrapping_add(*byte);
89        }
90
91        byte_stream[9] = (!checksum).wrapping_add(1);
92        byte_stream
93    }
94
95    pub fn add_object(&mut self, obj: &impl AmlObject) {
96        obj.append_to_vec(&mut self.objects);
97    }
98
99    /// Adds a PCI Express root complex with the specified bus number and MMIO ranges.
100    ///
101    /// ```text
102    /// Device(\_SB.PCI<N>)
103    /// {
104    ///     Name(_HID, PNP0A08)
105    ///     Name(_CID, PNP0A03)
106    ///     Name(_UID, <index>)
107    ///     Name(_SEG, <segment>)
108    ///     Name(_BBN, <bus number>)
109    ///     Name(_CRS, ResourceTemplate()
110    ///     {
111    ///         WordBusNumber(...) // Bus number range
112    ///         QWordMemory() // Low MMIO
113    ///         QWordMemory() // High MMIO
114    ///     })
115    /// }
116    /// ```
117    pub fn add_pcie(
118        &mut self,
119        index: u32,
120        segment: u16,
121        start_bus: u8,
122        end_bus: u8,
123        ecam_range: MemoryRange,
124        low_mmio: MemoryRange,
125        high_mmio: MemoryRange,
126    ) {
127        let mut pcie = Device::new(encode_pcie_name(index).as_slice());
128        pcie.add_object(&NamedObject::new(b"_HID", &EisaId(*b"PNP0A08")));
129        pcie.add_object(&NamedObject::new(b"_CID", &EisaId(*b"PNP0A03")));
130        pcie.add_object(&NamedInteger::new(b"_UID", index.into()));
131        pcie.add_object(&NamedInteger::new(b"_SEG", segment.into()));
132        pcie.add_object(&NamedInteger::new(b"_BBN", start_bus.into()));
133
134        // _OSC method: grant native PCIe control to the OS.
135        //
136        // Per ACPI spec §6.2.11 (_OSC, Operating System Capabilities), the OS
137        // calls _OSC to negotiate platform feature control. For PCIe root
138        // complexes, the UUID and capability definitions are specified in the
139        // PCI Firmware Specification §4.5.1 (_OSC Implementation for PCI Host
140        // Bridge Devices).
141        //
142        // UUID: 33DB4D5B-1FF7-401C-9657-7441C03DD766
143        //   (PCI Firmware Spec §4.5.1, Table 4-3)
144        //
145        // Status DWORD[0] bit 2: "Unrecognized UUID"
146        //   (ACPI spec §6.2.11.1, Table 6.15)
147        //
148        // Method(_OSC, 4) {
149        //   CreateDWordField(Arg3, 0, STS0)
150        //   If (LEqual(Arg0, ToUUID("33DB4D5B-1FF7-401C-9657-7441C03DD766"))) {
151        //     Store(0, STS0)  // clear status — grant everything
152        //   } Else {
153        //     Or(STS0, 0x04, STS0)  // unrecognized UUID
154        //   }
155        //   Return(Arg3)
156        // }
157        let mut osc_method = Method::new(b"_OSC");
158        osc_method.set_arg_count(4);
159
160        // CreateDWordField(Arg3, 0, STS0)
161        osc_method.add_operation(&CreateDWordFieldOp {
162            source_buffer: encode_arg(3),
163            byte_index: encode_integer(0),
164            field_name: *b"STS0",
165        });
166
167        // If (LEqual(Arg0, ToUUID("33DB4D5B-1FF7-401C-9657-7441C03DD766")))
168        let pcie_osc_uuid = guid::guid!("33DB4D5B-1FF7-401C-9657-7441C03DD766");
169        let uuid_buffer = Buffer(pcie_osc_uuid.as_bytes()).to_bytes();
170        let lequal = LEqualOp {
171            left: encode_arg(0),
172            right: uuid_buffer,
173        };
174
175        // Else { Or(STS0, 0x04, STS0) }
176        let or_op = OrOp {
177            operand1: b"STS0".to_vec(),
178            operand2: encode_integer(0x04),
179            target_name: b"STS0".to_vec(),
180        };
181        let else_body = ElseOp {
182            body: or_op.to_bytes(),
183        };
184
185        // If block: UUID matches — clear status and grant everything
186        let store_zero = StoreOp {
187            source: encode_integer(0),
188            destination: b"STS0".to_vec(),
189        };
190        let if_op = IfOp {
191            predicate: lequal.to_bytes(),
192            body: store_zero.to_bytes(),
193        };
194        osc_method.add_operation(&if_op);
195        osc_method.add_operation(&else_body);
196
197        // Return(Arg3)
198        osc_method.add_operation(&ReturnOp {
199            result: encode_arg(3),
200        });
201
202        pcie.add_object(&osc_method);
203
204        let mut crs = CurrentResourceSettings::new();
205        crs.add_resource(&BusNumber::new(
206            start_bus.into(),
207            (end_bus as u16) - (start_bus as u16) + 1,
208        ));
209        crs.add_resource(&QwordMemory::new(
210            low_mmio.start(),
211            low_mmio.end() - low_mmio.start(),
212        ));
213        crs.add_resource(&QwordMemory::new(
214            high_mmio.start(),
215            high_mmio.end() - high_mmio.start(),
216        ));
217        pcie.add_object(&crs);
218
219        self.add_object(&pcie);
220        self.pcie_ecam_ranges.push(ecam_range);
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227    use crate::aml::test_helpers::verify_expected_bytes;
228
229    pub fn verify_header(bytes: &[u8]) {
230        assert!(bytes.len() >= 36);
231
232        // signature
233        assert_eq!(bytes[0], b'S');
234        assert_eq!(bytes[1], b'S');
235        assert_eq!(bytes[2], b'D');
236        assert_eq!(bytes[3], b'T');
237
238        // length
239        let ssdt_len = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
240        assert_eq!(ssdt_len as usize, bytes.len());
241
242        // revision
243        assert_eq!(bytes[8], 2);
244
245        // Validate checksum bytes[9] by verifying content adds to zero.
246        let mut checksum: u8 = 0;
247        for byte in bytes.iter() {
248            checksum = checksum.wrapping_add(*byte);
249        }
250        assert_eq!(checksum, 0);
251
252        // oem_id
253        assert_eq!(bytes[10], b'M');
254        assert_eq!(bytes[11], b'S');
255        assert_eq!(bytes[12], b'F');
256        assert_eq!(bytes[13], b'T');
257        assert_eq!(bytes[14], b'V');
258        assert_eq!(bytes[15], b'M');
259
260        // oem_table_id
261        assert_eq!(bytes[16], b'S');
262        assert_eq!(bytes[17], b'S');
263        assert_eq!(bytes[18], b'D');
264        assert_eq!(bytes[19], b'T');
265        assert_eq!(bytes[20], b'0');
266        assert_eq!(bytes[21], b'1');
267        assert_eq!(bytes[22], 0);
268        assert_eq!(bytes[23], 0);
269
270        // oem_revision
271        let oem_revision = u32::from_le_bytes(bytes[24..28].try_into().unwrap());
272        assert_eq!(oem_revision, 1);
273
274        // creator_id
275        assert_eq!(bytes[28], b'M');
276        assert_eq!(bytes[29], b'S');
277        assert_eq!(bytes[30], b'F');
278        assert_eq!(bytes[31], b'T');
279
280        // creator_rev
281        let creator_rev = u32::from_le_bytes(bytes[32..36].try_into().unwrap());
282        assert_eq!(creator_rev, 0x01000000);
283    }
284
285    #[test]
286    pub fn verify_pcie_name_encoding() {
287        assert_eq!(encode_pcie_name(0), b"PCI0".to_vec());
288        assert_eq!(encode_pcie_name(1), b"PCI1".to_vec());
289        assert_eq!(encode_pcie_name(2), b"PCI2".to_vec());
290        assert_eq!(encode_pcie_name(54), b"PC54".to_vec());
291        assert_eq!(encode_pcie_name(294), b"P294".to_vec());
292    }
293
294    #[test]
295    fn verify_simple_table() {
296        let mut ssdt = Ssdt::new();
297        let nobj = NamedObject::new(b"_S0", &Package(vec![0, 0]));
298        ssdt.add_object(&nobj);
299        let bytes = ssdt.to_bytes();
300        verify_header(&bytes);
301        verify_expected_bytes(&bytes[36..], &[8, b'_', b'S', b'0', b'_', 0x12, 4, 2, 0, 0]);
302    }
303}