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