Skip to main content

chipset_legacy/piix4_pm/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PIIX4 - Power Management
5
6pub mod resolver;
7
8use chipset::pm::PowerAction;
9use chipset::pm::PowerManagementDevice;
10use chipset_device::ChipsetDevice;
11use chipset_device::interrupt::LineInterruptTarget;
12use chipset_device::io::IoError;
13use chipset_device::io::IoResult;
14use chipset_device::pci::ByteEnabledDwordRead;
15use chipset_device::pci::ByteEnabledDwordWrite;
16use chipset_device::pci::PciConfigSpace;
17use chipset_device::pio::ControlPortIoIntercept;
18use chipset_device::pio::PortIoIntercept;
19use chipset_device::pio::RegisterPortIoIntercept;
20use inspect::Inspect;
21use inspect::InspectMut;
22use open_enum::open_enum;
23use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
24use pci_core::cfg_space_emu::DeviceBars;
25use pci_core::spec::hwid::ClassCode;
26use pci_core::spec::hwid::HardwareIds;
27use pci_core::spec::hwid::ProgrammingInterface;
28use pci_core::spec::hwid::Subclass;
29use vmcore::device_state::ChangeDeviceState;
30
31/// IO ports used in the legacy Hyper-V implementation
32pub mod io_ports {
33    // TODO: add an assert during PM construction that enforces this \/
34    // N.B. PM_BASE must be a multiple of 0x100 since the PM device looks at the bottom byte
35    // to determine the offset. It also must be >= 0x100 so that it doesn't overlap
36    // with the status or control port.
37    //
38    // N.B. Note that these values must also match what is reported in UEFI, as these
39    // values are also reported in the FADT.
40    // MsvmPkg: PowerManagementInterface.h
41    // MsvmPkg: Fadt.aslc
42    pub const DEFAULT_DYN_BASE: u16 = 0x400;
43
44    pub const CONTROL_PORT: u16 = 0xB2;
45    pub const STATUS_PORT: u16 = 0xB3;
46}
47
48#[derive(Debug)]
49enum StaticReg {
50    Control,
51    Status,
52}
53
54struct Piix4PmRt {
55    pio_static_control: Box<dyn ControlPortIoIntercept>,
56    pio_static_status: Box<dyn ControlPortIoIntercept>,
57}
58
59impl std::fmt::Debug for Piix4PmRt {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        f.debug_struct("Piix4PmRt").finish()
62    }
63}
64
65#[derive(Debug, Inspect)]
66struct Piix4PmState {
67    power_status: u8,
68    power_control: u8,
69
70    smbus_io_enabled: bool,
71    #[inspect(hex)]
72    base_io_addr: u16,
73    base_io_enable: bool,
74    counter_info_a: u32,
75    counter_info_b: u32,
76    general_purpose_config_info: u32,
77    #[inspect(hex, iter_by_index)]
78    device_resource_flags: [u32; 10],
79    #[inspect(hex, iter_by_index)]
80    device_activity_flags: [u32; 2],
81}
82
83impl Piix4PmState {
84    fn new() -> Self {
85        Self {
86            power_status: 0,
87            power_control: 0,
88
89            smbus_io_enabled: false,
90            base_io_addr: 0,
91            base_io_enable: false,
92            counter_info_a: 0,
93            counter_info_b: 0,
94            general_purpose_config_info: 0,
95            device_resource_flags: [0; 10],
96            device_activity_flags: [0; 2],
97        }
98    }
99}
100
101/// PIIX4 (PCI device function 3) - Power Management
102///
103/// See section 3.4 in the PIIX4 data sheet.
104#[derive(InspectMut)]
105pub struct Piix4Pm {
106    // Runtime glue
107    #[inspect(skip)]
108    rt: Piix4PmRt,
109
110    // Sub-emulators
111    #[inspect(mut)]
112    inner: PowerManagementDevice,
113    cfg_space: ConfigSpaceType0Emulator,
114
115    // Volatile state
116    state: Piix4PmState,
117}
118
119impl Piix4Pm {
120    /// Create a new PIIX4 PM device wrapping a pre-constructed
121    /// [`PowerManagementDevice`].
122    ///
123    /// The `inner` PM device should be constructed by the resolver
124    /// infrastructure (see [`chipset::pm::resolver::resolve_pm_deps`])
125    /// with `enable_acpi_mode` set to `None`, as the PIIX4 variant manages
126    /// ACPI mode transitions through its own PCI config space.
127    pub fn new(
128        inner: PowerManagementDevice,
129        register_pio: &mut dyn RegisterPortIoIntercept,
130    ) -> Self {
131        let cfg_space = ConfigSpaceType0Emulator::new(
132            HardwareIds {
133                vendor_id: 0x8086,
134                device_id: 0x7113,
135                revision_id: 0x02,
136                prog_if: ProgrammingInterface::NONE,
137                sub_class: Subclass::BRIDGE_OTHER,
138                base_class: ClassCode::BRIDGE,
139                type0_sub_vendor_id: 0,
140                type0_sub_system_id: 0,
141            },
142            Vec::new(),
143            Vec::new(),
144            DeviceBars::new(),
145        );
146
147        let mut pio_static_control = register_pio.new_io_region("control", 1);
148        let mut pio_static_status = register_pio.new_io_region("status", 1);
149
150        pio_static_control.map(io_ports::CONTROL_PORT);
151        pio_static_status.map(io_ports::STATUS_PORT);
152
153        Self {
154            inner,
155            cfg_space,
156            rt: Piix4PmRt {
157                pio_static_control,
158                pio_static_status,
159            },
160            state: Piix4PmState::new(),
161        }
162    }
163
164    fn update_io_mappings(&mut self) {
165        if self.state.base_io_enable && self.state.base_io_addr != 0 {
166            self.inner
167                .update_dynamic_pio_mappings(Some(self.state.base_io_addr))
168        } else {
169            self.inner.update_dynamic_pio_mappings(None)
170        }
171    }
172
173    fn read_static(&mut self, reg: StaticReg, data: &mut [u8]) {
174        if data.len() != 1 {
175            tracelimit::warn_ratelimited!(?reg, ?data, "unexpected read");
176            return;
177        }
178
179        data[0] = match reg {
180            StaticReg::Control => self.state.power_control,
181            StaticReg::Status => self.state.power_status,
182        }
183    }
184
185    fn write_static(&mut self, reg: StaticReg, data: &[u8]) {
186        if data.len() != 1 {
187            tracelimit::warn_ratelimited!(?reg, ?data, "unexpected write");
188            return;
189        }
190
191        let data = data[0];
192        match reg {
193            StaticReg::Control => {
194                // If bit 25 is set in the Device Activity B register, we need
195                // to generate an SMI on writes to port 0xB2 (the APMC).
196                if self.state.device_activity_flags[1] & 1 << 25 != 0 {
197                    // Normally, a write to port 0xB2 would generate an SMI which would
198                    // invoke the ACPI BIOS. Virtualizing SMI is difficult, so we'll just
199                    // emulate the important side-effects of the ACPI routines. The only
200                    // important side effect expected by ACPI-aware OSes is that the SCI_EN
201                    // bit in the power management control register is set and the PM timer
202                    // overflow is enabled.
203                    //
204                    // The values 0xE1 and 0x1E are not defined by the chipset. Rather, they
205                    // come from the system BIOS's ACPI table. If the BIOS is modified, the
206                    // values below should be changed to match the ACPI_ENABLE and ACPI_DISABLE
207                    // parameters within the FACP (fixed ACPI description) table.
208                    if data == 0xE1 {
209                        self.inner.pcat_facp_acpi_enable(true);
210                    } else if data == 0x1E {
211                        self.inner.pcat_facp_acpi_enable(false);
212                    }
213                }
214
215                let old_control = self.state.power_control;
216                self.state.power_control = data;
217
218                // Handle accesses to the control port. This kludge was originally
219                // only in the MR BIOS, so we could conditionalize based on this.
220                // However, the OS/2 additions created by Innotek now use this kludge
221                // too, so we need to keep it around. - Eric
222                if old_control == b'E' && self.state.power_control == b'T' {
223                    // If the sequence 'ET' is written to this port, the BIOS
224                    // is telling us to power down. We will just quit. Note that
225                    // this is not standard. Typically, the Triton chip set does
226                    // not provide a way for desktop machines to power down.
227                    // However, we have modified the BIOS to accept the power-
228                    // down command (INT 15_5307, bx=0001, cx=0003) and generate
229                    // this I/O write pattern.
230                    (self.inner.power_action())(PowerAction::PowerOff)
231                }
232            }
233            StaticReg::Status => self.state.power_status = data,
234        }
235    }
236}
237
238impl ChangeDeviceState for Piix4Pm {
239    fn start(&mut self) {}
240
241    async fn stop(&mut self) {}
242
243    async fn reset(&mut self) {
244        self.inner.reset().await;
245        self.cfg_space.reset();
246        self.state = Piix4PmState::new();
247
248        self.update_io_mappings()
249    }
250}
251
252impl ChipsetDevice for Piix4Pm {
253    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
254        Some(self)
255    }
256
257    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
258        Some(self)
259    }
260
261    fn supports_line_interrupt_target(&mut self) -> Option<&mut dyn LineInterruptTarget> {
262        Some(self)
263    }
264}
265
266impl PortIoIntercept for Piix4Pm {
267    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
268        // 1-byte control register
269        if let Some(0) = self.rt.pio_static_control.offset_of(io_port) {
270            self.read_static(StaticReg::Control, data);
271            return IoResult::Ok;
272        }
273
274        // 1-byte status register
275        if let Some(0) = self.rt.pio_static_status.offset_of(io_port) {
276            self.read_static(StaticReg::Status, data);
277            return IoResult::Ok;
278        }
279
280        self.inner.io_read(io_port, data)
281    }
282
283    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
284        // 1-byte control register
285        if let Some(0) = self.rt.pio_static_control.offset_of(io_port) {
286            self.write_static(StaticReg::Control, data);
287
288            self.inner.check_interrupt_assertion();
289            return IoResult::Ok;
290        }
291
292        // 1-byte status register
293        if let Some(0) = self.rt.pio_static_status.offset_of(io_port) {
294            self.write_static(StaticReg::Status, data);
295
296            self.inner.check_interrupt_assertion();
297            return IoResult::Ok;
298        }
299
300        self.inner.io_write(io_port, data)
301    }
302}
303
304/// Target for lines corresponding to bits in General Purpose Event Block 0.
305///
306/// For a specific description of an implementation of this, see the PIIX4
307/// manual, section 7.2. The PIIX4 manual calls this register the "General
308/// Purpose Status Register"
309impl LineInterruptTarget for Piix4Pm {
310    fn set_irq(&mut self, vector: u32, high: bool) {
311        LineInterruptTarget::set_irq(&mut self.inner, vector, high)
312    }
313
314    fn valid_lines(&self) -> &[std::ops::RangeInclusive<u32>] {
315        // PIIX4 manual dictates all other bits are marked as reserved.
316        &[0..=0, 8..=11]
317    }
318}
319
320/// Sidestep the config space emulator, and match legacy stub behavior directly
321impl PciConfigSpace for Piix4Pm {
322    fn pci_cfg_read(&mut self, offset: u16, mut value: ByteEnabledDwordRead<'_>) -> IoResult {
323        value.set(match ConfigSpace(offset) {
324            // for bug-for-bug compat with the hyper-v implementation: return
325            // hardcoded status register instead of letting the config space
326            // emulator take care of it
327            _ if offset == pci_core::spec::cfg_space::HeaderType00::STATUS_COMMAND.0 => {
328                let mut v = 0x02800000;
329                if self.state.smbus_io_enabled {
330                    v |= 1;
331                }
332                v
333            }
334            // ditto for the latency/interrupt register
335            _ if offset == pci_core::spec::cfg_space::HeaderType00::LATENCY_INTERRUPT.0 => {
336                // report that the device is hard-wired to PCI interrupt lane A
337                // (even though we don't actually use the IRQ for anything)
338                let res = self.cfg_space.read_byte_enabled(offset, value.reborrow());
339                value.set(value.extract() & 0xff | (1 << 8));
340                return res;
341            }
342            _ if offset < 0x40 => return self.cfg_space.read_byte_enabled(offset, value),
343            // The bottom bit is always 1 to indicate an I/O address.
344            ConfigSpace::IO_BASE => self.state.base_io_addr as u32 | 1,
345            ConfigSpace::COUNT_A => self.state.counter_info_a,
346            ConfigSpace::COUNT_B => self.state.counter_info_b,
347            ConfigSpace::GENERAL_PURPOSE => self.state.general_purpose_config_info,
348            ConfigSpace::ACTIVITY_A => self.state.device_activity_flags[0],
349            ConfigSpace::ACTIVITY_B => self.state.device_activity_flags[1],
350            ConfigSpace::RESOURCE_A => self.state.device_resource_flags[0],
351            ConfigSpace::RESOURCE_B => self.state.device_resource_flags[1],
352            ConfigSpace::RESOURCE_C => self.state.device_resource_flags[2],
353            ConfigSpace::RESOURCE_D => self.state.device_resource_flags[3],
354            ConfigSpace::RESOURCE_E => self.state.device_resource_flags[4],
355            ConfigSpace::RESOURCE_F => self.state.device_resource_flags[5],
356            ConfigSpace::RESOURCE_G => self.state.device_resource_flags[6],
357            ConfigSpace::RESOURCE_H => self.state.device_resource_flags[7],
358            ConfigSpace::RESOURCE_I => self.state.device_resource_flags[8],
359            ConfigSpace::RESOURCE_J => self.state.device_resource_flags[9],
360            ConfigSpace::IO_ENABLE => self.state.base_io_enable as u32,
361            ConfigSpace::SM_BASE | ConfigSpace::SM_HOST => 0, // Hyper-V always returns 0, so do we.
362            _ => {
363                tracing::debug!(?offset, "unimplemented config space read");
364                return IoResult::Err(IoError::InvalidRegister);
365            }
366        });
367
368        IoResult::Ok
369    }
370
371    fn pci_cfg_write(&mut self, offset: u16, value: ByteEnabledDwordWrite) -> IoResult {
372        match ConfigSpace(offset) {
373            // intercept smbus_io_enabled bit
374            // We don't have SMBus support, but the bit still needs to get latched.
375            _ if offset == pci_core::spec::cfg_space::HeaderType00::STATUS_COMMAND.0 => {
376                if value.valid_mask() & 1 != 0 {
377                    self.state.smbus_io_enabled = value.extract() & 1 != 0;
378                }
379                return self.cfg_space.write_byte_enabled(offset, value);
380            }
381            _ if offset < 0x40 => return self.cfg_space.write_byte_enabled(offset, value),
382            ConfigSpace::IO_BASE => {
383                // mask off the read-only bits
384                //
385                // NOTE: this implies that the base address of the pm device
386                // will always be a multiple of 0x100
387                let value = value.merge_low(self.state.base_io_addr);
388                self.state.base_io_addr = value & 0xFFC0;
389                self.update_io_mappings()
390            }
391            ConfigSpace::COUNT_A => value.merge_into(&mut self.state.counter_info_a),
392            ConfigSpace::COUNT_B => value.merge_into(&mut self.state.counter_info_b),
393            ConfigSpace::GENERAL_PURPOSE => {
394                value.merge_into(&mut self.state.general_purpose_config_info)
395            }
396            ConfigSpace::ACTIVITY_A => value.merge_into(&mut self.state.device_activity_flags[0]),
397            ConfigSpace::ACTIVITY_B => value.merge_into(&mut self.state.device_activity_flags[1]),
398            ConfigSpace::RESOURCE_A => value.merge_into(&mut self.state.device_resource_flags[0]),
399            ConfigSpace::RESOURCE_B => value.merge_into(&mut self.state.device_resource_flags[1]),
400            ConfigSpace::RESOURCE_C => value.merge_into(&mut self.state.device_resource_flags[2]),
401            ConfigSpace::RESOURCE_D => value.merge_into(&mut self.state.device_resource_flags[3]),
402            ConfigSpace::RESOURCE_E => value.merge_into(&mut self.state.device_resource_flags[4]),
403            ConfigSpace::RESOURCE_F => value.merge_into(&mut self.state.device_resource_flags[5]),
404            ConfigSpace::RESOURCE_G => value.merge_into(&mut self.state.device_resource_flags[6]),
405            ConfigSpace::RESOURCE_H => value.merge_into(&mut self.state.device_resource_flags[7]),
406            ConfigSpace::RESOURCE_I => value.merge_into(&mut self.state.device_resource_flags[8]),
407            ConfigSpace::RESOURCE_J => value.merge_into(&mut self.state.device_resource_flags[9]),
408            ConfigSpace::IO_ENABLE => {
409                if value.valid_mask() & 1 != 0 {
410                    self.state.base_io_enable = value.extract() & 1 != 0;
411                }
412                self.update_io_mappings()
413            }
414            ConfigSpace::SM_BASE | ConfigSpace::SM_HOST => {} // Hyper-V ignores these, so do we.
415            _ => {
416                tracelimit::warn_ratelimited!(?offset, ?value, "unimplemented config space write");
417                return IoResult::Err(IoError::InvalidRegister);
418            }
419        }
420
421        IoResult::Ok
422    }
423
424    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
425        Some((0, 7, 3)) // as per PIIX4 spec
426    }
427}
428
429open_enum! {
430    enum ConfigSpace: u16 {
431        IO_BASE         = 0x40,
432        COUNT_A         = 0x44,
433        COUNT_B         = 0x48,
434        GENERAL_PURPOSE = 0x4C,
435        RESOURCE_D      = 0x50,
436        ACTIVITY_A      = 0x54,
437        ACTIVITY_B      = 0x58,
438        RESOURCE_A      = 0x5C,
439        RESOURCE_B      = 0x60,
440        RESOURCE_C      = 0x64,
441        RESOURCE_E      = 0x68,
442        RESOURCE_F      = 0x6C,
443        RESOURCE_G      = 0x70,
444        RESOURCE_H      = 0x74,
445        RESOURCE_I      = 0x78,
446        RESOURCE_J      = 0x7C,
447        IO_ENABLE       = 0x80,
448        SM_BASE         = 0x90,
449        SM_HOST         = 0xD0,
450    }
451}
452
453mod save_restore {
454    use super::*;
455    use vmcore::save_restore::RestoreError;
456    use vmcore::save_restore::SaveError;
457    use vmcore::save_restore::SaveRestore;
458
459    mod state {
460        use chipset::pm::PowerManagementDevice;
461        use mesh::payload::Protobuf;
462        use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
463        use vmcore::save_restore::SaveRestore;
464        use vmcore::save_restore::SavedStateRoot;
465
466        #[derive(Protobuf, SavedStateRoot)]
467        #[mesh(package = "chipset.piix4.pm")]
468        pub struct SavedState {
469            #[mesh(1)]
470            pub power_status: u8,
471            #[mesh(2)]
472            pub power_control: u8,
473            #[mesh(3)]
474            pub smbus_io_enabled: bool,
475            #[mesh(4)]
476            pub base_io_addr: u16,
477            #[mesh(5)]
478            pub base_io_enable: bool,
479            #[mesh(6)]
480            pub counter_info_a: u32,
481            #[mesh(7)]
482            pub counter_info_b: u32,
483            #[mesh(8)]
484            pub general_purpose_config_info: u32,
485            #[mesh(9)]
486            pub device_resource_flags: [u32; 10],
487            #[mesh(10)]
488            pub device_activity_flags: [u32; 2],
489            #[mesh(11)]
490            pub cfg_space: <ConfigSpaceType0Emulator as SaveRestore>::SavedState,
491            #[mesh(12)]
492            pub inner: <PowerManagementDevice as SaveRestore>::SavedState,
493        }
494    }
495
496    impl SaveRestore for Piix4Pm {
497        type SavedState = state::SavedState;
498
499        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
500            let Piix4PmState {
501                power_status,
502                power_control,
503                smbus_io_enabled,
504                base_io_addr,
505                base_io_enable,
506                counter_info_a,
507                counter_info_b,
508                general_purpose_config_info,
509                device_resource_flags,
510                device_activity_flags,
511            } = self.state;
512
513            let saved_state = state::SavedState {
514                power_status,
515                power_control,
516                smbus_io_enabled,
517                base_io_addr,
518                base_io_enable,
519                counter_info_a,
520                counter_info_b,
521                general_purpose_config_info,
522                device_resource_flags,
523                device_activity_flags,
524                cfg_space: self.cfg_space.save()?,
525                inner: self.inner.save()?,
526            };
527
528            Ok(saved_state)
529        }
530
531        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
532            let state::SavedState {
533                power_status,
534                power_control,
535                smbus_io_enabled,
536                base_io_addr,
537                base_io_enable,
538                counter_info_a,
539                counter_info_b,
540                general_purpose_config_info,
541                device_resource_flags,
542                device_activity_flags,
543                cfg_space,
544                inner,
545            } = state;
546
547            let state = Piix4PmState {
548                power_status,
549                power_control,
550                smbus_io_enabled,
551                base_io_addr,
552                base_io_enable,
553                counter_info_a,
554                counter_info_b,
555                general_purpose_config_info,
556                device_resource_flags,
557                device_activity_flags,
558            };
559
560            self.state = state;
561
562            self.update_io_mappings();
563            self.cfg_space.restore(cfg_space)?;
564            self.inner.restore(inner)?;
565            Ok(())
566        }
567    }
568}