xref: /cloud-hypervisor/devices/src/acpi.rs (revision 19d36c765fdf00be749d95b3e61028bc302d6d73)
1 // Copyright © 2019 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use std::sync::atomic::{AtomicBool, Ordering};
7 use std::sync::{Arc, Barrier};
8 use std::thread;
9 use std::time::Instant;
10 
11 use acpi_tables::{aml, Aml, AmlSink};
12 use vm_device::interrupt::InterruptSourceGroup;
13 use vm_device::BusDevice;
14 use vm_memory::GuestAddress;
15 use vmm_sys_util::eventfd::EventFd;
16 
17 use super::AcpiNotificationFlags;
18 
19 pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
20 
21 /// A device for handling ACPI shutdown and reboot
22 pub struct AcpiShutdownDevice {
23     exit_evt: EventFd,
24     reset_evt: EventFd,
25     vcpus_kill_signalled: Arc<AtomicBool>,
26 }
27 
28 impl AcpiShutdownDevice {
29     /// Constructs a device that will signal the given event when the guest requests it.
30     pub fn new(
31         exit_evt: EventFd,
32         reset_evt: EventFd,
33         vcpus_kill_signalled: Arc<AtomicBool>,
34     ) -> AcpiShutdownDevice {
35         AcpiShutdownDevice {
36             exit_evt,
37             reset_evt,
38             vcpus_kill_signalled,
39         }
40     }
41 }
42 
43 // Same I/O port used for shutdown and reboot
44 impl BusDevice for AcpiShutdownDevice {
45     // Spec has all fields as zero
46     fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
47         data.fill(0)
48     }
49 
50     fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
51         if data[0] == 1 {
52             info!("ACPI Reboot signalled");
53             if let Err(e) = self.reset_evt.write(1) {
54                 error!("Error triggering ACPI reset event: {}", e);
55             }
56             // Spin until we are sure the reset_evt has been handled and that when
57             // we return from the KVM_RUN we will exit rather than re-enter the guest.
58             while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
59                 // This is more effective than thread::yield_now() at
60                 // avoiding a priority inversion with the VMM thread
61                 thread::sleep(std::time::Duration::from_millis(1));
62             }
63         }
64         // The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
65         const S5_SLEEP_VALUE: u8 = 5;
66         const SLEEP_STATUS_EN_BIT: u8 = 5;
67         const SLEEP_VALUE_BIT: u8 = 2;
68         if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
69             info!("ACPI Shutdown signalled");
70             if let Err(e) = self.exit_evt.write(1) {
71                 error!("Error triggering ACPI shutdown event: {}", e);
72             }
73             // Spin until we are sure the reset_evt has been handled and that when
74             // we return from the KVM_RUN we will exit rather than re-enter the guest.
75             while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
76                 // This is more effective than thread::yield_now() at
77                 // avoiding a priority inversion with the VMM thread
78                 thread::sleep(std::time::Duration::from_millis(1));
79             }
80         }
81         None
82     }
83 }
84 
85 /// A device for handling ACPI GED event generation
86 pub struct AcpiGedDevice {
87     interrupt: Arc<dyn InterruptSourceGroup>,
88     notification_type: AcpiNotificationFlags,
89     ged_irq: u32,
90     address: GuestAddress,
91 }
92 
93 impl AcpiGedDevice {
94     pub fn new(
95         interrupt: Arc<dyn InterruptSourceGroup>,
96         ged_irq: u32,
97         address: GuestAddress,
98     ) -> AcpiGedDevice {
99         AcpiGedDevice {
100             interrupt,
101             notification_type: AcpiNotificationFlags::NO_DEVICES_CHANGED,
102             ged_irq,
103             address,
104         }
105     }
106 
107     pub fn notify(
108         &mut self,
109         notification_type: AcpiNotificationFlags,
110     ) -> Result<(), std::io::Error> {
111         self.notification_type |= notification_type;
112         self.interrupt.trigger(0)
113     }
114 
115     pub fn irq(&self) -> u32 {
116         self.ged_irq
117     }
118 }
119 
120 // I/O port reports what type of notification was made
121 impl BusDevice for AcpiGedDevice {
122     // Spec has all fields as zero
123     fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
124         data[0] = self.notification_type.bits();
125         self.notification_type = AcpiNotificationFlags::NO_DEVICES_CHANGED;
126     }
127 }
128 
129 impl Aml for AcpiGedDevice {
130     fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
131         aml::Device::new(
132             "_SB_.GEC_".into(),
133             vec![
134                 &aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
135                 &aml::Name::new("_UID".into(), &"Generic Event Controller"),
136                 &aml::Name::new(
137                     "_CRS".into(),
138                     &aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
139                         aml::AddressSpaceCacheable::NotCacheable,
140                         true,
141                         self.address.0,
142                         self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
143                         None,
144                     )]),
145                 ),
146                 &aml::OpRegion::new(
147                     "GDST".into(),
148                     aml::OpRegionSpace::SystemMemory,
149                     &(self.address.0 as usize),
150                     &GED_DEVICE_ACPI_SIZE,
151                 ),
152                 &aml::Field::new(
153                     "GDST".into(),
154                     aml::FieldAccessType::Byte,
155                     aml::FieldLockRule::NoLock,
156                     aml::FieldUpdateRule::WriteAsZeroes,
157                     vec![aml::FieldEntry::Named(*b"GDAT", 8)],
158                 ),
159                 &aml::Method::new(
160                     "ESCN".into(),
161                     0,
162                     true,
163                     vec![
164                         &aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
165                         &aml::And::new(&aml::Local(1), &aml::Local(0), &aml::ONE),
166                         &aml::If::new(
167                             &aml::Equal::new(&aml::Local(1), &aml::ONE),
168                             vec![&aml::MethodCall::new("\\_SB_.CPUS.CSCN".into(), vec![])],
169                         ),
170                         &aml::And::new(&aml::Local(1), &aml::Local(0), &2usize),
171                         &aml::If::new(
172                             &aml::Equal::new(&aml::Local(1), &2usize),
173                             vec![&aml::MethodCall::new("\\_SB_.MHPC.MSCN".into(), vec![])],
174                         ),
175                         &aml::And::new(&aml::Local(1), &aml::Local(0), &4usize),
176                         &aml::If::new(
177                             &aml::Equal::new(&aml::Local(1), &4usize),
178                             vec![&aml::MethodCall::new("\\_SB_.PHPR.PSCN".into(), vec![])],
179                         ),
180                         &aml::And::new(&aml::Local(1), &aml::Local(0), &8usize),
181                         &aml::If::new(
182                             &aml::Equal::new(&aml::Local(1), &8usize),
183                             vec![&aml::Notify::new(
184                                 &aml::Path::new("\\_SB_.PWRB"),
185                                 &0x80usize,
186                             )],
187                         ),
188                     ],
189                 ),
190             ],
191         )
192         .to_aml_bytes(sink);
193         aml::Device::new(
194             "_SB_.GED_".into(),
195             vec![
196                 &aml::Name::new("_HID".into(), &"ACPI0013"),
197                 &aml::Name::new("_UID".into(), &aml::ZERO),
198                 &aml::Name::new(
199                     "_CRS".into(),
200                     &aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
201                         true,
202                         true,
203                         false,
204                         false,
205                         self.ged_irq,
206                     )]),
207                 ),
208                 &aml::Method::new(
209                     "_EVT".into(),
210                     1,
211                     true,
212                     vec![&aml::MethodCall::new("\\_SB_.GEC_.ESCN".into(), vec![])],
213                 ),
214             ],
215         )
216         .to_aml_bytes(sink)
217     }
218 }
219 
220 pub struct AcpiPmTimerDevice {
221     start: Instant,
222 }
223 
224 impl AcpiPmTimerDevice {
225     pub fn new() -> Self {
226         Self {
227             start: Instant::now(),
228         }
229     }
230 }
231 
232 impl Default for AcpiPmTimerDevice {
233     fn default() -> Self {
234         Self::new()
235     }
236 }
237 
238 impl BusDevice for AcpiPmTimerDevice {
239     fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
240         if data.len() != std::mem::size_of::<u32>() {
241             warn!("Invalid sized read of PM timer: {}", data.len());
242             return;
243         }
244         let now = Instant::now();
245         let since = now.duration_since(self.start);
246         let nanos = since.as_nanos();
247 
248         const PM_TIMER_FREQUENCY_HZ: u128 = 3_579_545;
249         const NANOS_PER_SECOND: u128 = 1_000_000_000;
250 
251         let counter = (nanos * PM_TIMER_FREQUENCY_HZ) / NANOS_PER_SECOND;
252         let counter: u32 = (counter & 0xffff_ffff) as u32;
253 
254         data.copy_from_slice(&counter.to_le_bytes());
255     }
256 }
257