xref: /cloud-hypervisor/devices/src/pvpanic.rs (revision ed63b352d1ebf70f36c7d36a0d6b52fc96186581)
1 // Copyright © 2023 Tencent Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use std::any::Any;
7 use std::result;
8 use std::sync::{Arc, Barrier, Mutex};
9 
10 use anyhow::anyhow;
11 use pci::{
12     BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
13     PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
14     PCI_CONFIGURATION_ID,
15 };
16 use serde::{Deserialize, Serialize};
17 use thiserror::Error;
18 use vm_allocator::{AddressAllocator, SystemAllocator};
19 use vm_device::{BusDevice, Resource};
20 use vm_memory::{Address, GuestAddress};
21 use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
22 
23 const PVPANIC_VENDOR_ID: u16 = 0x1b36;
24 const PVPANIC_DEVICE_ID: u16 = 0x0011;
25 
26 pub const PVPANIC_DEVICE_MMIO_SIZE: u64 = 0x2;
27 pub const PVPANIC_DEVICE_MMIO_ALIGNMENT: u64 = 0x10;
28 
29 const PVPANIC_PANICKED: u8 = 1 << 0;
30 const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
31 
32 #[derive(Debug, Error)]
33 pub enum PvPanicError {
34     #[error("Failed creating PvPanicDevice")]
35     CreatePvPanicDevice(#[source] anyhow::Error),
36     #[error("Failed to retrieve PciConfigurationState")]
37     RetrievePciConfigurationState(#[source] anyhow::Error),
38 }
39 
40 #[derive(Copy, Clone)]
41 enum PvPanicSubclass {
42     Other = 0x80,
43 }
44 
45 impl PciSubclass for PvPanicSubclass {
46     fn get_register_value(&self) -> u8 {
47         *self as u8
48     }
49 }
50 
51 /// A device for handling guest panic event
52 pub struct PvPanicDevice {
53     id: String,
54     events: u8,
55 
56     // PCI configuration registers.
57     configuration: PciConfiguration,
58     bar_regions: Vec<PciBarConfiguration>,
59 }
60 
61 #[derive(Serialize, Deserialize)]
62 pub struct PvPanicDeviceState {
63     events: u8,
64 }
65 
66 impl PvPanicDevice {
67     pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
68         let pci_configuration_state =
69             vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
70                 PvPanicError::RetrievePciConfigurationState(anyhow!(
71                     "Failed to get PciConfigurationState from Snapshot: {}",
72                     e
73                 ))
74             })?;
75 
76         let mut configuration = PciConfiguration::new(
77             PVPANIC_VENDOR_ID,
78             PVPANIC_DEVICE_ID,
79             0x1, // modern pci devices
80             PciClassCode::BaseSystemPeripheral,
81             &PvPanicSubclass::Other,
82             None,
83             PciHeaderType::Device,
84             0,
85             0,
86             None,
87             pci_configuration_state,
88         );
89 
90         let command: [u8; 2] = [0x03, 0x01];
91         let bar_reprogram = configuration.write_config_register(1, 0, &command);
92         assert!(
93             bar_reprogram.is_empty(),
94             "No bar reprogrammig is expected from writing to the COMMAND register"
95         );
96 
97         let state: Option<PvPanicDeviceState> = snapshot
98             .as_ref()
99             .map(|s| s.to_state())
100             .transpose()
101             .map_err(|e| {
102                 PvPanicError::CreatePvPanicDevice(anyhow!(
103                     "Failed to get PvPanicDeviceState from Snapshot: {}",
104                     e
105                 ))
106             })?;
107         let events = if let Some(state) = state {
108             state.events
109         } else {
110             PVPANIC_PANICKED | PVPANIC_CRASH_LOADED
111         };
112 
113         let pvpanic_device = PvPanicDevice {
114             id,
115             events,
116             configuration,
117             bar_regions: vec![],
118         };
119 
120         Ok(pvpanic_device)
121     }
122 
123     pub fn event_to_string(&self, event: u8) -> String {
124         if event == PVPANIC_PANICKED {
125             "panic".to_string()
126         } else if event == PVPANIC_CRASH_LOADED {
127             "crash_loaded".to_string()
128         } else {
129             "unknown_event".to_string()
130         }
131     }
132 
133     fn state(&self) -> PvPanicDeviceState {
134         PvPanicDeviceState {
135             events: self.events,
136         }
137     }
138 
139     pub fn config_bar_addr(&self) -> u64 {
140         self.configuration.get_bar_addr(0)
141     }
142 }
143 
144 impl BusDevice for PvPanicDevice {
145     fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
146         self.read_bar(base, offset, data)
147     }
148 
149     fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
150         let event = self.event_to_string(data[0]);
151         info!("pvpanic got guest event {}", event);
152         event!("guest", "panic", "event", &event);
153         None
154     }
155 }
156 
157 impl PciDevice for PvPanicDevice {
158     fn write_config_register(
159         &mut self,
160         reg_idx: usize,
161         offset: u64,
162         data: &[u8],
163     ) -> (Vec<BarReprogrammingParams>, Option<Arc<Barrier>>) {
164         (
165             self.configuration
166                 .write_config_register(reg_idx, offset, data),
167             None,
168         )
169     }
170 
171     fn read_config_register(&mut self, reg_idx: usize) -> u32 {
172         self.configuration.read_reg(reg_idx)
173     }
174 
175     fn allocate_bars(
176         &mut self,
177         _allocator: &Arc<Mutex<SystemAllocator>>,
178         mmio32_allocator: &mut AddressAllocator,
179         _mmio64_allocator: &mut AddressAllocator,
180         resources: Option<Vec<Resource>>,
181     ) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
182         let mut bars = Vec::new();
183         let region_type = PciBarRegionType::Memory32BitRegion;
184         let bar_id = 0;
185         let region_size = PVPANIC_DEVICE_MMIO_SIZE;
186         let restoring = resources.is_some();
187         let bar_addr = mmio32_allocator
188             .allocate(None, region_size, Some(PVPANIC_DEVICE_MMIO_ALIGNMENT))
189             .ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
190 
191         let bar = PciBarConfiguration::default()
192             .set_index(bar_id as usize)
193             .set_address(bar_addr.raw_value())
194             .set_size(region_size)
195             .set_region_type(region_type)
196             .set_prefetchable(PciBarPrefetchable::NotPrefetchable);
197 
198         debug!("pvpanic bar address 0x{:x}", bar_addr.0);
199         if !restoring {
200             self.configuration
201                 .add_pci_bar(&bar)
202                 .map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
203         }
204 
205         bars.push(bar);
206         self.bar_regions.clone_from(&bars);
207 
208         Ok(bars)
209     }
210 
211     fn free_bars(
212         &mut self,
213         _allocator: &mut SystemAllocator,
214         mmio32_allocator: &mut AddressAllocator,
215         _mmio64_allocator: &mut AddressAllocator,
216     ) -> std::result::Result<(), PciDeviceError> {
217         for bar in self.bar_regions.drain(..) {
218             mmio32_allocator.free(GuestAddress(bar.addr()), bar.size());
219         }
220 
221         Ok(())
222     }
223 
224     fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
225         for bar in self.bar_regions.iter_mut() {
226             if bar.addr() == old_base {
227                 *bar = bar.set_address(new_base);
228             }
229         }
230 
231         Ok(())
232     }
233 
234     fn read_bar(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
235         data[0] = self.events;
236     }
237 
238     fn as_any_mut(&mut self) -> &mut dyn Any {
239         self
240     }
241 
242     fn id(&self) -> Option<String> {
243         Some(self.id.clone())
244     }
245 }
246 
247 impl Pausable for PvPanicDevice {}
248 
249 impl Snapshottable for PvPanicDevice {
250     fn id(&self) -> String {
251         self.id.clone()
252     }
253 
254     fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
255         let mut snapshot = Snapshot::new_from_state(&self.state())?;
256 
257         // Snapshot PciConfiguration
258         snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
259 
260         Ok(snapshot)
261     }
262 }
263 
264 impl Transportable for PvPanicDevice {}
265 impl Migratable for PvPanicDevice {}
266