/src/crosvm/devices/src/pci/pvpanic.rs
Line | Count | Source |
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 | | //! pvpanic is a simulated device, through which a guest panic event is sent to a VMM. |
6 | | //! This was initially developed for qemu with linux in-tree drivers and opensource |
7 | | //! driver for windows also exist now. |
8 | | //! <https://fossies.org/linux/qemu/docs/specs/pvpanic.txt> |
9 | | //! |
10 | | //! This implementation emulates pci interface for pvpanic virtual device. |
11 | | |
12 | | // TODO(218575411): Support pvpanic on windows crosvm. |
13 | | #![cfg_attr(windows, allow(dead_code))] |
14 | | |
15 | | use std::fmt; |
16 | | |
17 | | use anyhow::Context; |
18 | | use base::error; |
19 | | use base::RawDescriptor; |
20 | | use base::SendTube; |
21 | | use base::SharedMemory; |
22 | | use base::VmEventType; |
23 | | use resources::Alloc; |
24 | | use resources::AllocOptions; |
25 | | use resources::SystemAllocator; |
26 | | use snapshot::AnySnapshot; |
27 | | |
28 | | use crate::pci::pci_configuration::PciBarConfiguration; |
29 | | use crate::pci::pci_configuration::PciBarPrefetchable; |
30 | | use crate::pci::pci_configuration::PciBarRegionType; |
31 | | use crate::pci::pci_configuration::PciClassCode; |
32 | | use crate::pci::pci_configuration::PciConfiguration; |
33 | | use crate::pci::pci_configuration::PciHeaderType; |
34 | | use crate::pci::pci_configuration::PciOtherSubclass; |
35 | | use crate::pci::pci_device; |
36 | | use crate::pci::pci_device::BarRange; |
37 | | use crate::pci::pci_device::PciDevice; |
38 | | use crate::pci::pci_device::Result; |
39 | | use crate::pci::PciAddress; |
40 | | use crate::pci::PciBarIndex; |
41 | | use crate::pci::PciDeviceError; |
42 | | use crate::pci::PCI_VENDOR_ID_REDHAT; |
43 | | use crate::Suspendable; |
44 | | |
45 | | const PCI_DEVICE_ID_REDHAT_PVPANIC: u16 = 0x0011; |
46 | | const PCI_PVPANIC_REVISION_ID: u8 = 1; |
47 | | |
48 | | const PVPANIC_BAR_INDEX: PciBarIndex = 0; |
49 | | const PVPANIC_REG_SIZE: u64 = 0x10; |
50 | | |
51 | | // Guest panicked |
52 | | pub const PVPANIC_PANICKED: u8 = 1 << 0; |
53 | | // Guest kexeced crash kernel |
54 | | pub const PVPANIC_CRASH_LOADED: u8 = 1 << 1; |
55 | | |
56 | | const PVPANIC_CAPABILITIES: u8 = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED; |
57 | | |
58 | | #[repr(u8)] |
59 | | #[derive(PartialEq, Eq)] |
60 | | pub enum PvPanicCode { |
61 | | Panicked = PVPANIC_PANICKED, |
62 | | CrashLoaded = PVPANIC_CRASH_LOADED, |
63 | | Unknown = 0xFF, |
64 | | } |
65 | | |
66 | | impl PvPanicCode { |
67 | 0 | pub fn from_u8(val: u8) -> PvPanicCode { |
68 | 0 | match val { |
69 | 0 | PVPANIC_PANICKED => PvPanicCode::Panicked, |
70 | 0 | PVPANIC_CRASH_LOADED => PvPanicCode::CrashLoaded, |
71 | 0 | _ => PvPanicCode::Unknown, |
72 | | } |
73 | 0 | } |
74 | | } |
75 | | |
76 | | impl fmt::Display for PvPanicCode { |
77 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
78 | 0 | match self { |
79 | 0 | PvPanicCode::Panicked => write!(f, "Guest panicked"), |
80 | 0 | PvPanicCode::CrashLoaded => write!(f, "Guest panicked and crash kernel loaded"), |
81 | 0 | PvPanicCode::Unknown => write!(f, "Guest panicked with unknown code"), |
82 | | } |
83 | 0 | } |
84 | | } |
85 | | |
86 | | pub struct PvPanicPciDevice { |
87 | | pci_address: Option<PciAddress>, |
88 | | config_regs: PciConfiguration, |
89 | | evt_wrtube: SendTube, |
90 | | } |
91 | | |
92 | | impl PvPanicPciDevice { |
93 | 0 | pub fn new(evt_wrtube: SendTube) -> PvPanicPciDevice { |
94 | 0 | let config_regs = PciConfiguration::new( |
95 | | PCI_VENDOR_ID_REDHAT, |
96 | | PCI_DEVICE_ID_REDHAT_PVPANIC, |
97 | 0 | PciClassCode::Other, |
98 | 0 | &PciOtherSubclass::Other, |
99 | 0 | None, |
100 | 0 | PciHeaderType::Device, |
101 | | 0xFF, |
102 | | 0xFF, |
103 | | PCI_PVPANIC_REVISION_ID, |
104 | | ); |
105 | | |
106 | 0 | Self { |
107 | 0 | pci_address: None, |
108 | 0 | config_regs, |
109 | 0 | evt_wrtube, |
110 | 0 | } |
111 | 0 | } |
112 | | } |
113 | | |
114 | | impl PciDevice for PvPanicPciDevice { |
115 | 0 | fn debug_label(&self) -> String { |
116 | 0 | "PvPanic".to_owned() |
117 | 0 | } |
118 | | |
119 | 0 | fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> { |
120 | 0 | if self.pci_address.is_none() { |
121 | 0 | self.pci_address = resources.allocate_pci(0, self.debug_label()); |
122 | 0 | } |
123 | 0 | self.pci_address.ok_or(PciDeviceError::PciAllocationFailed) |
124 | 0 | } |
125 | | |
126 | 0 | fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<BarRange>> { |
127 | 0 | let address = self |
128 | 0 | .pci_address |
129 | 0 | .expect("allocate_address must be called prior to allocate_io_bars"); |
130 | 0 | let mut ranges: Vec<BarRange> = Vec::new(); |
131 | 0 | let pvpanic_reg_addr = resources |
132 | 0 | .allocate_mmio( |
133 | | PVPANIC_REG_SIZE, |
134 | 0 | Alloc::PciBar { |
135 | 0 | bus: address.bus, |
136 | 0 | dev: address.dev, |
137 | 0 | func: address.func, |
138 | 0 | bar: PVPANIC_BAR_INDEX as u8, |
139 | 0 | }, |
140 | 0 | "pvpanic_reg".to_string(), |
141 | 0 | AllocOptions::new() |
142 | 0 | .max_address(u32::MAX.into()) |
143 | 0 | .align(PVPANIC_REG_SIZE), |
144 | | ) |
145 | 0 | .map_err(|e| pci_device::Error::IoAllocationFailed(PVPANIC_REG_SIZE, e))?; |
146 | 0 | let pvpanic_config = PciBarConfiguration::new( |
147 | | PVPANIC_BAR_INDEX, |
148 | | PVPANIC_REG_SIZE, |
149 | 0 | PciBarRegionType::Memory32BitRegion, |
150 | 0 | PciBarPrefetchable::NotPrefetchable, |
151 | | ) |
152 | 0 | .set_address(pvpanic_reg_addr); |
153 | 0 | self.config_regs |
154 | 0 | .add_pci_bar(pvpanic_config) |
155 | 0 | .map_err(|e| pci_device::Error::IoRegistrationFailed(pvpanic_reg_addr, e))?; |
156 | 0 | ranges.push(BarRange { |
157 | 0 | addr: pvpanic_reg_addr, |
158 | 0 | size: PVPANIC_REG_SIZE, |
159 | 0 | prefetchable: false, |
160 | 0 | }); |
161 | | |
162 | 0 | Ok(ranges) |
163 | 0 | } |
164 | | |
165 | 0 | fn keep_rds(&self) -> Vec<RawDescriptor> { |
166 | 0 | Vec::new() |
167 | 0 | } |
168 | | |
169 | 0 | fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> { |
170 | 0 | self.config_regs.get_bar_configuration(bar_num) |
171 | 0 | } |
172 | | |
173 | 0 | fn read_config_register(&self, reg_idx: usize) -> u32 { |
174 | 0 | self.config_regs.read_reg(reg_idx) |
175 | 0 | } |
176 | | |
177 | 0 | fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) { |
178 | 0 | self.config_regs.write_reg(reg_idx, offset, data); |
179 | 0 | } |
180 | | |
181 | 0 | fn setup_pci_config_mapping( |
182 | 0 | &mut self, |
183 | 0 | shmem: &SharedMemory, |
184 | 0 | base: usize, |
185 | 0 | len: usize, |
186 | 0 | ) -> Result<bool> { |
187 | 0 | self.config_regs |
188 | 0 | .setup_mapping(shmem, base, len) |
189 | 0 | .map(|_| true) |
190 | 0 | .map_err(PciDeviceError::MmioSetup) |
191 | 0 | } |
192 | | |
193 | 0 | fn read_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &mut [u8]) { |
194 | 0 | data[0] = if bar_index == PVPANIC_BAR_INDEX && offset == 0 && data.len() == 1 { |
195 | 0 | PVPANIC_CAPABILITIES |
196 | | } else { |
197 | 0 | 0 |
198 | | }; |
199 | 0 | } |
200 | | |
201 | 0 | fn write_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &[u8]) { |
202 | 0 | if bar_index != PVPANIC_BAR_INDEX || offset != 0 || data.len() != 1 { |
203 | 0 | return; |
204 | 0 | } |
205 | | |
206 | 0 | if let Err(e) = self |
207 | 0 | .evt_wrtube |
208 | 0 | .send::<VmEventType>(&VmEventType::Panic(data[0])) |
209 | | { |
210 | 0 | error!("Failed to write to the event tube: {}", e); |
211 | 0 | } |
212 | 0 | } |
213 | | } |
214 | | |
215 | | impl Suspendable for PvPanicPciDevice { |
216 | 0 | fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> { |
217 | 0 | self.config_regs |
218 | 0 | .snapshot() |
219 | 0 | .context("failed to serialize PvPanicPciDevice") |
220 | 0 | } |
221 | | |
222 | 0 | fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> { |
223 | 0 | self.config_regs |
224 | 0 | .restore(data) |
225 | 0 | .context("failed to deserialize PvPanicPciDevice") |
226 | 0 | } |
227 | | |
228 | 0 | fn sleep(&mut self) -> anyhow::Result<()> { |
229 | 0 | Ok(()) |
230 | 0 | } |
231 | | |
232 | 0 | fn wake(&mut self) -> anyhow::Result<()> { |
233 | 0 | Ok(()) |
234 | 0 | } |
235 | | } |
236 | | |
237 | | #[cfg(test)] |
238 | | mod test { |
239 | | use base::Tube; |
240 | | use resources::AddressRange; |
241 | | use resources::SystemAllocator; |
242 | | use resources::SystemAllocatorConfig; |
243 | | |
244 | | use super::*; |
245 | | |
246 | | #[test] |
247 | | fn pvpanic_read_write() { |
248 | | let mut allocator = SystemAllocator::new( |
249 | | SystemAllocatorConfig { |
250 | | io: Some(AddressRange { |
251 | | start: 0x1000, |
252 | | end: 0xffff, |
253 | | }), |
254 | | low_mmio: AddressRange { |
255 | | start: 0x2000_0000, |
256 | | end: 0x2fffffff, |
257 | | }, |
258 | | high_mmio: AddressRange { |
259 | | start: 0x1_0000_0000, |
260 | | end: 0x1_0fff_ffff, |
261 | | }, |
262 | | platform_mmio: None, |
263 | | first_irq: 5, |
264 | | }, |
265 | | None, |
266 | | &[], |
267 | | ) |
268 | | .unwrap(); |
269 | | |
270 | | let (evt_wrtube, evt_rdtube) = Tube::directional_pair().unwrap(); |
271 | | let mut device = PvPanicPciDevice::new(evt_wrtube); |
272 | | |
273 | | assert!(device.allocate_address(&mut allocator).is_ok()); |
274 | | assert!(device.allocate_io_bars(&mut allocator).is_ok()); |
275 | | |
276 | | let mut data: [u8; 1] = [0; 1]; |
277 | | |
278 | | // Read from an invalid addr |
279 | | device.read_bar(0, 1, &mut data); |
280 | | assert_eq!(data[0], 0); |
281 | | |
282 | | // Read from the valid addr |
283 | | device.read_bar(0, 0, &mut data); |
284 | | assert_eq!(data[0], PVPANIC_CAPABILITIES); |
285 | | |
286 | | // Write to the valid addr. |
287 | | data[0] = PVPANIC_CRASH_LOADED; |
288 | | device.write_bar(0, 0, &data); |
289 | | |
290 | | // Verify the event |
291 | | let val = evt_rdtube.recv::<VmEventType>().unwrap(); |
292 | | assert_eq!(val, VmEventType::Panic(PVPANIC_CRASH_LOADED)); |
293 | | } |
294 | | } |