chipset_legacy/
piix4_pm.rs

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