devices/pci/
pm.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![cfg_attr(windows, allow(dead_code))]
6
7use zerocopy::FromBytes;
8use zerocopy::Immutable;
9use zerocopy::IntoBytes;
10use zerocopy::KnownLayout;
11
12use crate::pci::pci_configuration::PciCapConfig;
13use crate::pci::pci_configuration::PciCapConfigWriteResult;
14use crate::pci::pci_configuration::PciCapMapping;
15use crate::pci::PciCapability;
16use crate::pci::PciCapabilityID;
17
18const PM_CAP_CONTROL_STATE_OFFSET: usize = 1;
19pub const PM_CAP_LENGTH: usize = 8;
20const PM_CAP_PME_SUPPORT_D0: u16 = 0x0800;
21const PM_CAP_PME_SUPPORT_D3_HOT: u16 = 0x4000;
22const PM_CAP_PME_SUPPORT_D3_COLD: u16 = 0x8000;
23const PM_CAP_VERSION: u16 = 0x2;
24const PM_PME_STATUS: u16 = 0x8000;
25const PM_PME_ENABLE: u16 = 0x100;
26const PM_NO_SOFT_RESET: u16 = 0x8;
27const PM_POWER_STATE_MASK: u16 = 0x3;
28const PM_POWER_STATE_D0: u16 = 0;
29const PM_POWER_STATE_D3: u16 = 0x3;
30
31#[derive(Eq, PartialEq)]
32pub enum PciDevicePower {
33    D0 = 0,
34    D3 = 3,
35    Unsupported = 0xFF,
36}
37
38#[repr(C)]
39#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
40pub struct PciPmCap {
41    _cap_vndr: u8,
42    _cap_next: u8,
43    pm_cap: u16,
44    pm_control_status: u16,
45    padding: u16,
46}
47
48impl PciCapability for PciPmCap {
49    fn bytes(&self) -> &[u8] {
50        self.as_bytes()
51    }
52
53    fn id(&self) -> PciCapabilityID {
54        PciCapabilityID::PowerManagement
55    }
56
57    fn writable_bits(&self) -> Vec<u32> {
58        vec![0u32, 0x8103]
59    }
60}
61
62impl PciPmCap {
63    pub fn new() -> Self {
64        PciPmCap {
65            _cap_vndr: 0,
66            _cap_next: 0,
67            pm_cap: Self::default_cap(),
68            pm_control_status: 0,
69            padding: 0,
70        }
71    }
72    pub fn default_cap() -> u16 {
73        let mut cap = PM_CAP_VERSION;
74        // We use ACPI GPEs for PMEs, which are x86_64 only. We should
75        // implement support for native PCIe PMEs to support non-ACPI platforms.
76        if cfg!(target_arch = "x86_64") {
77            cap |= PM_CAP_PME_SUPPORT_D0 | PM_CAP_PME_SUPPORT_D3_HOT | PM_CAP_PME_SUPPORT_D3_COLD
78        }
79        cap
80    }
81}
82
83pub struct PmConfig {
84    power_control_status: u16,
85    cap_mapping: Option<PciCapMapping>,
86}
87
88impl PmConfig {
89    pub fn new(no_soft_reset: bool) -> Self {
90        PmConfig {
91            power_control_status: if no_soft_reset { PM_NO_SOFT_RESET } else { 0 },
92            cap_mapping: None,
93        }
94    }
95
96    pub fn read(&self, data: &mut u32) {
97        *data = self.power_control_status as u32;
98    }
99
100    pub fn write(&mut self, offset: u64, data: &[u8]) {
101        if offset > 1 {
102            return;
103        }
104
105        if offset == 0 {
106            self.power_control_status &= !PM_POWER_STATE_MASK;
107            self.power_control_status |= data[0] as u16 & PM_POWER_STATE_MASK;
108        }
109
110        let write_data = if offset == 0 && (data.len() == 2 || data.len() == 4) {
111            Some((data[1] as u16) << 8)
112        } else if offset == 1 && data.len() == 1 {
113            Some((data[0] as u16) << 8)
114        } else {
115            None
116        };
117
118        if let Some(write_data) = write_data {
119            if write_data & PM_PME_STATUS != 0 {
120                // clear PME_STATUS
121                self.power_control_status &= !PM_PME_STATUS;
122            }
123
124            if write_data & PM_PME_ENABLE != 0 {
125                self.power_control_status |= PM_PME_ENABLE;
126            } else {
127                self.power_control_status &= !PM_PME_ENABLE;
128            }
129        }
130    }
131
132    /// If device is in D3 and PME is enabled, set PME status, then device could
133    /// inject a pme interrupt into guest
134    pub fn should_trigger_pme(&mut self) -> bool {
135        if self.power_control_status & PM_POWER_STATE_MASK == PM_POWER_STATE_D3
136            && self.power_control_status & PM_PME_ENABLE != 0
137        {
138            self.power_control_status |= PM_PME_STATUS;
139            if let Some(cap_mapping) = &mut self.cap_mapping {
140                cap_mapping.set_reg(
141                    PM_CAP_CONTROL_STATE_OFFSET,
142                    self.power_control_status as u32,
143                    0xffff,
144                );
145            }
146            return true;
147        }
148
149        false
150    }
151
152    /// Get device power status
153    pub fn get_power_status(&self) -> PciDevicePower {
154        match self.power_control_status & PM_POWER_STATE_MASK {
155            PM_POWER_STATE_D0 => PciDevicePower::D0,
156            PM_POWER_STATE_D3 => PciDevicePower::D3,
157            _ => PciDevicePower::Unsupported,
158        }
159    }
160}
161
162pub struct PmStatusChange {
163    pub from: PciDevicePower,
164    pub to: PciDevicePower,
165}
166
167impl PciCapConfigWriteResult for PmStatusChange {}
168
169const PM_CONFIG_READ_MASK: [u32; 2] = [0, 0xffff];
170
171impl PciCapConfig for PmConfig {
172    fn read_mask(&self) -> &'static [u32] {
173        &PM_CONFIG_READ_MASK
174    }
175
176    fn read_reg(&self, reg_idx: usize) -> u32 {
177        let mut data = 0;
178        if reg_idx == PM_CAP_CONTROL_STATE_OFFSET {
179            self.read(&mut data);
180        }
181        data
182    }
183
184    fn write_reg(
185        &mut self,
186        reg_idx: usize,
187        offset: u64,
188        data: &[u8],
189    ) -> Option<Box<dyn PciCapConfigWriteResult>> {
190        if reg_idx == PM_CAP_CONTROL_STATE_OFFSET {
191            let from = self.get_power_status();
192            self.write(offset, data);
193            let to = self.get_power_status();
194            if from != to {
195                return Some(Box::new(PmStatusChange { from, to }));
196            }
197        }
198        None
199    }
200
201    fn set_cap_mapping(&mut self, mapping: PciCapMapping) {
202        self.cap_mapping = Some(mapping);
203    }
204}