devices/pci/
pvpanic.rs

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
15use std::fmt;
16
17use anyhow::Context;
18use base::error;
19use base::RawDescriptor;
20use base::SendTube;
21use base::SharedMemory;
22use base::VmEventType;
23use resources::Alloc;
24use resources::AllocOptions;
25use resources::SystemAllocator;
26use snapshot::AnySnapshot;
27
28use crate::pci::pci_configuration::PciBarConfiguration;
29use crate::pci::pci_configuration::PciBarPrefetchable;
30use crate::pci::pci_configuration::PciBarRegionType;
31use crate::pci::pci_configuration::PciClassCode;
32use crate::pci::pci_configuration::PciConfiguration;
33use crate::pci::pci_configuration::PciHeaderType;
34use crate::pci::pci_configuration::PciOtherSubclass;
35use crate::pci::pci_device;
36use crate::pci::pci_device::BarRange;
37use crate::pci::pci_device::PciDevice;
38use crate::pci::pci_device::Result;
39use crate::pci::PciAddress;
40use crate::pci::PciBarIndex;
41use crate::pci::PciDeviceError;
42use crate::pci::PCI_VENDOR_ID_REDHAT;
43use crate::Suspendable;
44
45const PCI_DEVICE_ID_REDHAT_PVPANIC: u16 = 0x0011;
46const PCI_PVPANIC_REVISION_ID: u8 = 1;
47
48const PVPANIC_BAR_INDEX: PciBarIndex = 0;
49const PVPANIC_REG_SIZE: u64 = 0x10;
50
51// Guest panicked
52pub const PVPANIC_PANICKED: u8 = 1 << 0;
53// Guest kexeced crash kernel
54pub const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
55
56const PVPANIC_CAPABILITIES: u8 = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED;
57
58#[repr(u8)]
59#[derive(PartialEq, Eq)]
60pub enum PvPanicCode {
61    Panicked = PVPANIC_PANICKED,
62    CrashLoaded = PVPANIC_CRASH_LOADED,
63    Unknown = 0xFF,
64}
65
66impl PvPanicCode {
67    pub fn from_u8(val: u8) -> PvPanicCode {
68        match val {
69            PVPANIC_PANICKED => PvPanicCode::Panicked,
70            PVPANIC_CRASH_LOADED => PvPanicCode::CrashLoaded,
71            _ => PvPanicCode::Unknown,
72        }
73    }
74}
75
76impl fmt::Display for PvPanicCode {
77    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        match self {
79            PvPanicCode::Panicked => write!(f, "Guest panicked"),
80            PvPanicCode::CrashLoaded => write!(f, "Guest panicked and crash kernel loaded"),
81            PvPanicCode::Unknown => write!(f, "Guest panicked with unknown code"),
82        }
83    }
84}
85
86pub struct PvPanicPciDevice {
87    pci_address: Option<PciAddress>,
88    config_regs: PciConfiguration,
89    evt_wrtube: SendTube,
90}
91
92impl PvPanicPciDevice {
93    pub fn new(evt_wrtube: SendTube) -> PvPanicPciDevice {
94        let config_regs = PciConfiguration::new(
95            PCI_VENDOR_ID_REDHAT,
96            PCI_DEVICE_ID_REDHAT_PVPANIC,
97            PciClassCode::Other,
98            &PciOtherSubclass::Other,
99            None,
100            PciHeaderType::Device,
101            0xFF,
102            0xFF,
103            PCI_PVPANIC_REVISION_ID,
104        );
105
106        Self {
107            pci_address: None,
108            config_regs,
109            evt_wrtube,
110        }
111    }
112}
113
114impl PciDevice for PvPanicPciDevice {
115    fn debug_label(&self) -> String {
116        "PvPanic".to_owned()
117    }
118
119    fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> {
120        if self.pci_address.is_none() {
121            self.pci_address = resources.allocate_pci(0, self.debug_label());
122        }
123        self.pci_address.ok_or(PciDeviceError::PciAllocationFailed)
124    }
125
126    fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
127        let address = self
128            .pci_address
129            .expect("allocate_address must be called prior to allocate_io_bars");
130        let mut ranges: Vec<BarRange> = Vec::new();
131        let pvpanic_reg_addr = resources
132            .allocate_mmio(
133                PVPANIC_REG_SIZE,
134                Alloc::PciBar {
135                    bus: address.bus,
136                    dev: address.dev,
137                    func: address.func,
138                    bar: PVPANIC_BAR_INDEX as u8,
139                },
140                "pvpanic_reg".to_string(),
141                AllocOptions::new()
142                    .max_address(u32::MAX.into())
143                    .align(PVPANIC_REG_SIZE),
144            )
145            .map_err(|e| pci_device::Error::IoAllocationFailed(PVPANIC_REG_SIZE, e))?;
146        let pvpanic_config = PciBarConfiguration::new(
147            PVPANIC_BAR_INDEX,
148            PVPANIC_REG_SIZE,
149            PciBarRegionType::Memory32BitRegion,
150            PciBarPrefetchable::NotPrefetchable,
151        )
152        .set_address(pvpanic_reg_addr);
153        self.config_regs
154            .add_pci_bar(pvpanic_config)
155            .map_err(|e| pci_device::Error::IoRegistrationFailed(pvpanic_reg_addr, e))?;
156        ranges.push(BarRange {
157            addr: pvpanic_reg_addr,
158            size: PVPANIC_REG_SIZE,
159            prefetchable: false,
160        });
161
162        Ok(ranges)
163    }
164
165    fn keep_rds(&self) -> Vec<RawDescriptor> {
166        Vec::new()
167    }
168
169    fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
170        self.config_regs.get_bar_configuration(bar_num)
171    }
172
173    fn read_config_register(&self, reg_idx: usize) -> u32 {
174        self.config_regs.read_reg(reg_idx)
175    }
176
177    fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
178        self.config_regs.write_reg(reg_idx, offset, data);
179    }
180
181    fn setup_pci_config_mapping(
182        &mut self,
183        shmem: &SharedMemory,
184        base: usize,
185        len: usize,
186    ) -> Result<bool> {
187        self.config_regs
188            .setup_mapping(shmem, base, len)
189            .map(|_| true)
190            .map_err(PciDeviceError::MmioSetup)
191    }
192
193    fn read_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &mut [u8]) {
194        data[0] = if bar_index == PVPANIC_BAR_INDEX && offset == 0 && data.len() == 1 {
195            PVPANIC_CAPABILITIES
196        } else {
197            0
198        };
199    }
200
201    fn write_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &[u8]) {
202        if bar_index != PVPANIC_BAR_INDEX || offset != 0 || data.len() != 1 {
203            return;
204        }
205
206        if let Err(e) = self
207            .evt_wrtube
208            .send::<VmEventType>(&VmEventType::Panic(data[0]))
209        {
210            error!("Failed to write to the event tube: {}", e);
211        }
212    }
213}
214
215impl Suspendable for PvPanicPciDevice {
216    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
217        self.config_regs
218            .snapshot()
219            .context("failed to serialize PvPanicPciDevice")
220    }
221
222    fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
223        self.config_regs
224            .restore(data)
225            .context("failed to deserialize PvPanicPciDevice")
226    }
227
228    fn sleep(&mut self) -> anyhow::Result<()> {
229        Ok(())
230    }
231
232    fn wake(&mut self) -> anyhow::Result<()> {
233        Ok(())
234    }
235}
236
237#[cfg(test)]
238mod 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}