xref: /cloud-hypervisor/devices/src/pvpanic.rs (revision 2fe7f54ece2a8f0461ed29aaeab41614f1f2da75)
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: {0}")]
35     CreatePvPanicDevice(#[source] anyhow::Error),
36     #[error("Failed to retrieve PciConfigurationState: {0}")]
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         configuration.write_config_register(1, 0, &command);
92 
93         let state: Option<PvPanicDeviceState> = snapshot
94             .as_ref()
95             .map(|s| s.to_state())
96             .transpose()
97             .map_err(|e| {
98                 PvPanicError::CreatePvPanicDevice(anyhow!(
99                     "Failed to get PvPanicDeviceState from Snapshot: {}",
100                     e
101                 ))
102             })?;
103         let events = if let Some(state) = state {
104             state.events
105         } else {
106             PVPANIC_PANICKED | PVPANIC_CRASH_LOADED
107         };
108 
109         let pvpanic_device = PvPanicDevice {
110             id,
111             events,
112             configuration,
113             bar_regions: vec![],
114         };
115 
116         Ok(pvpanic_device)
117     }
118 
119     pub fn event_to_string(&self, event: u8) -> String {
120         if event == PVPANIC_PANICKED {
121             "panic".to_string()
122         } else if event == PVPANIC_CRASH_LOADED {
123             "crash_loaded".to_string()
124         } else {
125             "unknown_event".to_string()
126         }
127     }
128 
129     fn state(&self) -> PvPanicDeviceState {
130         PvPanicDeviceState {
131             events: self.events,
132         }
133     }
134 
135     pub fn config_bar_addr(&self) -> u64 {
136         self.configuration.get_bar_addr(0)
137     }
138 }
139 
140 impl BusDevice for PvPanicDevice {
141     fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
142         self.read_bar(base, offset, data)
143     }
144 
145     fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
146         let event = self.event_to_string(data[0]);
147         info!("pvpanic got guest event {}", event);
148         event!("guest", "panic", "event", &event);
149         None
150     }
151 }
152 
153 impl PciDevice for PvPanicDevice {
154     fn write_config_register(
155         &mut self,
156         reg_idx: usize,
157         offset: u64,
158         data: &[u8],
159     ) -> Option<Arc<Barrier>> {
160         self.configuration
161             .write_config_register(reg_idx, offset, data);
162         None
163     }
164 
165     fn read_config_register(&mut self, reg_idx: usize) -> u32 {
166         self.configuration.read_reg(reg_idx)
167     }
168 
169     fn detect_bar_reprogramming(
170         &mut self,
171         reg_idx: usize,
172         data: &[u8],
173     ) -> Option<BarReprogrammingParams> {
174         self.configuration.detect_bar_reprogramming(reg_idx, data)
175     }
176 
177     fn allocate_bars(
178         &mut self,
179         _allocator: &Arc<Mutex<SystemAllocator>>,
180         mmio32_allocator: &mut AddressAllocator,
181         _mmio64_allocator: &mut AddressAllocator,
182         resources: Option<Vec<Resource>>,
183     ) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
184         let mut bars = Vec::new();
185         let region_type = PciBarRegionType::Memory32BitRegion;
186         let bar_id = 0;
187         let region_size = PVPANIC_DEVICE_MMIO_SIZE;
188         let restoring = resources.is_some();
189         let bar_addr = mmio32_allocator
190             .allocate(None, region_size, Some(PVPANIC_DEVICE_MMIO_ALIGNMENT))
191             .ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
192 
193         let bar = PciBarConfiguration::default()
194             .set_index(bar_id as usize)
195             .set_address(bar_addr.raw_value())
196             .set_size(region_size)
197             .set_region_type(region_type)
198             .set_prefetchable(PciBarPrefetchable::NotPrefetchable);
199 
200         debug!("pvpanic bar address 0x{:x}", bar_addr.0);
201         if !restoring {
202             self.configuration
203                 .add_pci_bar(&bar)
204                 .map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
205         }
206 
207         bars.push(bar);
208         self.bar_regions.clone_from(&bars);
209 
210         Ok(bars)
211     }
212 
213     fn free_bars(
214         &mut self,
215         _allocator: &mut SystemAllocator,
216         mmio32_allocator: &mut AddressAllocator,
217         _mmio64_allocator: &mut AddressAllocator,
218     ) -> std::result::Result<(), PciDeviceError> {
219         for bar in self.bar_regions.drain(..) {
220             mmio32_allocator.free(GuestAddress(bar.addr()), bar.size());
221         }
222 
223         Ok(())
224     }
225 
226     fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
227         for bar in self.bar_regions.iter_mut() {
228             if bar.addr() == old_base {
229                 *bar = bar.set_address(new_base);
230             }
231         }
232 
233         Ok(())
234     }
235 
236     fn read_bar(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
237         data[0] = self.events;
238     }
239 
240     fn as_any_mut(&mut self) -> &mut dyn Any {
241         self
242     }
243 
244     fn id(&self) -> Option<String> {
245         Some(self.id.clone())
246     }
247 }
248 
249 impl Pausable for PvPanicDevice {}
250 
251 impl Snapshottable for PvPanicDevice {
252     fn id(&self) -> String {
253         self.id.clone()
254     }
255 
256     fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
257         let mut snapshot = Snapshot::new_from_state(&self.state())?;
258 
259         // Snapshot PciConfiguration
260         snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
261 
262         Ok(snapshot)
263     }
264 }
265 
266 impl Transportable for PvPanicDevice {}
267 impl Migratable for PvPanicDevice {}
268