Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/irqchip/whpx.rs
5394 views
1
// Copyright 2021 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
use std::convert::TryFrom;
6
use std::sync::Arc;
7
8
use anyhow::Context;
9
use serde::Deserialize;
10
use serde::Serialize;
11
use sync::Mutex;
12
13
cfg_if::cfg_if! {
14
if #[cfg(test)] {
15
use base::FakeClock as Clock;
16
} else {
17
use base::Clock;
18
}
19
}
20
use base::error;
21
use base::Error;
22
use base::Event;
23
use base::Result;
24
use base::Tube;
25
use hypervisor::whpx::WhpxVcpu;
26
use hypervisor::whpx::WhpxVm;
27
use hypervisor::IoapicState;
28
use hypervisor::IrqRoute;
29
use hypervisor::IrqSource;
30
use hypervisor::IrqSourceChip;
31
use hypervisor::LapicState;
32
use hypervisor::MPState;
33
use hypervisor::MsiAddressMessage;
34
use hypervisor::MsiDataMessage;
35
use hypervisor::PicSelect;
36
use hypervisor::PicState;
37
use hypervisor::PitState;
38
use hypervisor::Vcpu;
39
use hypervisor::VcpuX86_64;
40
use hypervisor::Vm;
41
use resources::SystemAllocator;
42
use snapshot::AnySnapshot;
43
44
use crate::irqchip::DelayedIoApicIrqEvents;
45
use crate::irqchip::InterruptData;
46
use crate::irqchip::InterruptDestination;
47
use crate::irqchip::Ioapic;
48
use crate::irqchip::IrqEvent;
49
use crate::irqchip::IrqEventIndex;
50
use crate::irqchip::Pic;
51
use crate::irqchip::Routes;
52
use crate::irqchip::VcpuRunState;
53
use crate::irqchip::IOAPIC_BASE_ADDRESS;
54
use crate::irqchip::IOAPIC_MEM_LENGTH_BYTES;
55
use crate::Bus;
56
use crate::IrqChip;
57
use crate::IrqChipCap;
58
use crate::IrqChipX86_64;
59
use crate::IrqEdgeEvent;
60
use crate::IrqEventSource;
61
use crate::IrqLevelEvent;
62
use crate::Pit;
63
use crate::PitError;
64
65
/// PIT channel 0 timer is connected to IRQ 0
66
const PIT_CHANNEL0_IRQ: u32 = 0;
67
/// TODO(b/187464379): we don't know what the right frequency is here, but 100MHz gives
68
/// us better results than the frequency that WSL seems to use, which is 500MHz.
69
const WHPX_LOCAL_APIC_EMULATION_APIC_FREQUENCY: u32 = 100_000_000;
70
71
/// The WhpxSplitIrqChip supports
72
pub struct WhpxSplitIrqChip {
73
vm: WhpxVm,
74
routes: Arc<Mutex<Routes>>,
75
pit: Arc<Mutex<Pit>>,
76
pic: Arc<Mutex<Pic>>,
77
ioapic: Arc<Mutex<Ioapic>>,
78
ioapic_pins: usize,
79
/// Delayed ioapic irq object, that contains the delayed events because the ioapic was locked
80
/// when service_irq was called on the irqchip. This prevents deadlocks when a Vcpu thread has
81
/// locked the ioapic and the ioapic sends a AddMsiRoute signal to the main thread (which
82
/// itself may be busy trying to call service_irq).
83
///
84
/// ## Note:
85
/// This lock may be locked by itself to access the `DelayedIoApicIrqEvents`. If accessed in
86
/// conjunction with the `irq_events` field, that lock should be taken first to prevent
87
/// deadlocks stemming from lock-ordering issues.
88
delayed_ioapic_irq_events: Arc<Mutex<DelayedIoApicIrqEvents>>,
89
/// Array of Events that devices will use to assert ioapic pins.
90
irq_events: Arc<Mutex<Vec<Option<IrqEvent>>>>,
91
}
92
93
impl WhpxSplitIrqChip {
94
/// Construct a new WhpxSplitIrqChip.
95
pub fn new(vm: WhpxVm, irq_tube: Tube, ioapic_pins: Option<usize>) -> Result<Self> {
96
let pit_evt = IrqEdgeEvent::new()?;
97
let pit = Pit::new(pit_evt.try_clone()?, Arc::new(Mutex::new(Clock::new()))).map_err(
98
|e| match e {
99
PitError::CloneEvent(err) => err,
100
PitError::CreateEvent(err) => err,
101
PitError::CreateWaitContext(err) => err,
102
PitError::WaitError(err) => err,
103
PitError::TimerCreateError(err) => err,
104
PitError::SpawnThread(_) => Error::new(libc::EIO),
105
},
106
)?;
107
108
let pit_event_source = IrqEventSource::from_device(&pit);
109
110
let ioapic_pins = ioapic_pins.unwrap_or(hypervisor::NUM_IOAPIC_PINS);
111
let ioapic = Ioapic::new(irq_tube, ioapic_pins)?;
112
113
let mut chip = WhpxSplitIrqChip {
114
vm,
115
routes: Arc::new(Mutex::new(Routes::new())),
116
pit: Arc::new(Mutex::new(pit)),
117
pic: Arc::new(Mutex::new(Pic::new())),
118
ioapic: Arc::new(Mutex::new(ioapic)),
119
ioapic_pins,
120
delayed_ioapic_irq_events: Arc::new(Mutex::new(DelayedIoApicIrqEvents::new()?)),
121
irq_events: Arc::new(Mutex::new(Vec::new())),
122
};
123
124
// This is equivalent to setting this in the blank Routes object above because
125
// WhpxSplitIrqChip doesn't interact with the WHPX API when setting routes, but in case
126
// that changes we do it this way.
127
chip.set_irq_routes(&Routes::default_pic_ioapic_routes(ioapic_pins))?;
128
129
// Add the pit
130
chip.register_edge_irq_event(PIT_CHANNEL0_IRQ, &pit_evt, pit_event_source)?;
131
Ok(chip)
132
}
133
134
/// Sends a Message Signaled Interrupt to one or more APICs. The WHPX API does not accept the
135
/// MSI address and data directly, so we must parse them and supply WHPX with the vector,
136
/// destination id, destination mode, trigger mode, and delivery mode (aka interrupt type).
137
fn send_msi(&self, addr: u32, data: u32) -> Result<()> {
138
let mut msi_addr = MsiAddressMessage::new();
139
msi_addr.set(0, 32, addr as u64);
140
let dest = InterruptDestination::try_from(&msi_addr).or(Err(Error::new(libc::EINVAL)))?;
141
142
let mut msi_data = MsiDataMessage::new();
143
msi_data.set(0, 32, data as u64);
144
let data = InterruptData::from(&msi_data);
145
146
self.vm.request_interrupt(
147
data.vector,
148
dest.dest_id,
149
dest.mode,
150
data.trigger,
151
data.delivery,
152
)
153
}
154
155
/// Return true if there is a pending interrupt for the specified vcpu. For WhpxSplitIrqChip
156
/// this calls interrupt_requested on the pic.
157
pub fn interrupt_requested(&self, vcpu_id: usize) -> bool {
158
// Pic interrupts for the split irqchip only go to vcpu 0
159
if vcpu_id != 0 {
160
return false;
161
}
162
self.pic.lock().interrupt_requested()
163
}
164
165
/// Check if the specified vcpu has any pending interrupts. Returns [`None`] for no interrupts,
166
/// otherwise [`Some::<u8>`] should be the injected interrupt vector. For [`WhpxSplitIrqChip`]
167
/// this calls `get_external_interrupt` on the pic.
168
pub fn get_external_interrupt(&self, vcpu_id: usize) -> Result<Option<u8>> {
169
// Pic interrupts for the split irqchip only go to vcpu 0
170
if vcpu_id != 0 {
171
return Ok(None);
172
}
173
if let Some(vector) = self.pic.lock().get_external_interrupt() {
174
Ok(Some(vector))
175
} else {
176
Ok(None)
177
}
178
}
179
}
180
181
impl WhpxSplitIrqChip {
182
fn register_irq_event(
183
&mut self,
184
irq: u32,
185
irq_event: &Event,
186
resample_event: Option<&Event>,
187
source: IrqEventSource,
188
) -> Result<Option<usize>> {
189
let mut evt = IrqEvent {
190
gsi: irq,
191
event: irq_event.try_clone()?,
192
resample_event: None,
193
source,
194
};
195
196
if let Some(resample_event) = resample_event {
197
evt.resample_event = Some(resample_event.try_clone()?);
198
}
199
200
let mut irq_events = self.irq_events.lock();
201
let index = irq_events.len();
202
irq_events.push(Some(evt));
203
Ok(Some(index))
204
}
205
206
fn unregister_irq_event(&mut self, irq: u32, irq_event: &Event) -> Result<()> {
207
let mut irq_events = self.irq_events.lock();
208
for (index, evt) in irq_events.iter().enumerate() {
209
if let Some(evt) = evt {
210
if evt.gsi == irq && irq_event.eq(&evt.event) {
211
irq_events[index] = None;
212
break;
213
}
214
}
215
}
216
Ok(())
217
}
218
}
219
220
/// This IrqChip only works with Whpx so we only implement it for WhpxVcpu.
221
impl IrqChip for WhpxSplitIrqChip {
222
fn add_vcpu(&mut self, _vcpu_id: usize, _vcpu: &dyn Vcpu) -> Result<()> {
223
// The WHPX API acts entirely on the VM partition, so we don't need to keep references to
224
// the vcpus.
225
Ok(())
226
}
227
228
fn register_edge_irq_event(
229
&mut self,
230
irq: u32,
231
irq_event: &IrqEdgeEvent,
232
source: IrqEventSource,
233
) -> Result<Option<IrqEventIndex>> {
234
self.register_irq_event(irq, irq_event.get_trigger(), None, source)
235
}
236
237
fn unregister_edge_irq_event(&mut self, irq: u32, irq_event: &IrqEdgeEvent) -> Result<()> {
238
self.unregister_irq_event(irq, irq_event.get_trigger())
239
}
240
241
fn register_level_irq_event(
242
&mut self,
243
irq: u32,
244
irq_event: &IrqLevelEvent,
245
source: IrqEventSource,
246
) -> Result<Option<IrqEventIndex>> {
247
self.register_irq_event(
248
irq,
249
irq_event.get_trigger(),
250
Some(irq_event.get_resample()),
251
source,
252
)
253
}
254
255
fn unregister_level_irq_event(&mut self, irq: u32, irq_event: &IrqLevelEvent) -> Result<()> {
256
self.unregister_irq_event(irq, irq_event.get_trigger())
257
}
258
259
fn route_irq(&mut self, route: IrqRoute) -> Result<()> {
260
self.routes.lock().add(route)
261
}
262
263
fn set_irq_routes(&mut self, routes: &[IrqRoute]) -> Result<()> {
264
self.routes.lock().replace_all(routes)
265
}
266
267
fn irq_event_tokens(&self) -> Result<Vec<(IrqEventIndex, IrqEventSource, Event)>> {
268
let mut tokens: Vec<(IrqEventIndex, IrqEventSource, Event)> = Vec::new();
269
for (index, evt) in self.irq_events.lock().iter().enumerate() {
270
if let Some(evt) = evt {
271
tokens.push((index, evt.source.clone(), evt.event.try_clone()?));
272
}
273
}
274
Ok(tokens)
275
}
276
277
fn service_irq(&mut self, irq: u32, level: bool) -> Result<()> {
278
for route in self.routes.lock()[irq as usize].iter() {
279
match *route {
280
IrqSource::Irqchip {
281
chip: IrqSourceChip::PicPrimary,
282
pin,
283
}
284
| IrqSource::Irqchip {
285
chip: IrqSourceChip::PicSecondary,
286
pin,
287
} => {
288
self.pic.lock().service_irq(pin as u8, level);
289
}
290
IrqSource::Irqchip {
291
chip: IrqSourceChip::Ioapic,
292
pin,
293
} => {
294
self.ioapic.lock().service_irq(pin as usize, level);
295
}
296
// service_irq's level parameter is ignored for MSIs. MSI data specifies the level.
297
IrqSource::Msi { address, data } => self.send_msi(address as u32, data)?,
298
_ => {
299
error!("Unexpected route source {:?}", route);
300
return Err(Error::new(libc::EINVAL));
301
}
302
}
303
}
304
Ok(())
305
}
306
307
/// Services an IRQ event by asserting then deasserting an IRQ line. The associated Event
308
/// that triggered the irq event will be read from. If the irq is associated with a resample
309
/// Event, then the deassert will only happen after an EOI is broadcast for a vector
310
/// associated with the irq line.
311
/// For WhpxSplitIrqChip, this function identifies the destination(s) of the irq: PIC, IOAPIC,
312
/// or APIC (MSI). If it's a PIC or IOAPIC route, we attempt to call service_irq on those
313
/// chips. If the IOAPIC is unable to be immediately locked, we add the irq to the
314
/// delayed_ioapic_irq_events (though we still read from the Event that triggered the irq
315
/// event). If it's an MSI route, we call send_msi to decode the MSI and send the interrupt
316
/// to WHPX.
317
fn service_irq_event(&mut self, event_index: IrqEventIndex) -> Result<()> {
318
let irq_events = self.irq_events.lock();
319
let evt = if let Some(evt) = &irq_events[event_index] {
320
evt
321
} else {
322
return Ok(());
323
};
324
evt.event.wait()?;
325
326
for route in self.routes.lock()[evt.gsi as usize].iter() {
327
match *route {
328
IrqSource::Irqchip {
329
chip: IrqSourceChip::PicPrimary,
330
pin,
331
}
332
| IrqSource::Irqchip {
333
chip: IrqSourceChip::PicSecondary,
334
pin,
335
} => {
336
let mut pic = self.pic.lock();
337
if evt.resample_event.is_some() {
338
pic.service_irq(pin as u8, true);
339
} else {
340
pic.service_irq(pin as u8, true);
341
pic.service_irq(pin as u8, false);
342
}
343
}
344
IrqSource::Irqchip {
345
chip: IrqSourceChip::Ioapic,
346
pin,
347
} => {
348
if let Ok(mut ioapic) = self.ioapic.try_lock() {
349
if evt.resample_event.is_some() {
350
ioapic.service_irq(pin as usize, true);
351
} else {
352
ioapic.service_irq(pin as usize, true);
353
ioapic.service_irq(pin as usize, false);
354
}
355
} else {
356
let mut delayed_events = self.delayed_ioapic_irq_events.lock();
357
delayed_events.events.push(event_index);
358
delayed_events.trigger.signal()?;
359
}
360
}
361
IrqSource::Msi { address, data } => self.send_msi(address as u32, data)?,
362
_ => {
363
error!("Unexpected route source {:?}", route);
364
return Err(Error::new(libc::EINVAL));
365
}
366
}
367
}
368
369
Ok(())
370
}
371
372
/// Broadcasts an end of interrupt. For WhpxSplitIrqChip this sends the EOI to the Ioapic.
373
fn broadcast_eoi(&self, vector: u8) -> Result<()> {
374
self.ioapic.lock().end_of_interrupt(vector);
375
Ok(())
376
}
377
378
/// Injects any pending interrupts for `vcpu`.
379
/// For WhpxSplitIrqChip this injects any PIC interrupts on vcpu_id 0.
380
fn inject_interrupts(&self, vcpu: &dyn Vcpu) -> Result<()> {
381
let vcpu: &WhpxVcpu = vcpu
382
.downcast_ref()
383
.expect("WhpxSplitIrqChip::add_vcpu called with non-WhpxVcpu");
384
let vcpu_id = vcpu.id();
385
if !self.interrupt_requested(vcpu_id) || !vcpu.ready_for_interrupt() {
386
return Ok(());
387
}
388
389
if let Some(vector) = self.get_external_interrupt(vcpu_id)? {
390
vcpu.interrupt(vector)?;
391
}
392
393
// The second interrupt request should be handled immediately, so ask vCPU to exit as soon
394
// as possible.
395
if self.interrupt_requested(vcpu_id) {
396
vcpu.set_interrupt_window_requested(true);
397
}
398
Ok(())
399
}
400
401
/// Notifies the irq chip that the specified VCPU has executed a halt instruction.
402
/// For WhpxSplitIrqChip this is a no-op because Whpx handles VCPU blocking.
403
fn halted(&self, _vcpu_id: usize) {}
404
405
/// Blocks until `vcpu` is in a runnable state or until interrupted by
406
/// `IrqChip::kick_halted_vcpus`. Returns `VcpuRunState::Runnable if vcpu is runnable, or
407
/// `VcpuRunState::Interrupted` if the wait was interrupted.
408
/// For WhpxSplitIrqChip this is a no-op and always returns Runnable because Whpx handles VCPU
409
/// blocking.
410
fn wait_until_runnable(&self, _vcpu: &dyn Vcpu) -> Result<VcpuRunState> {
411
Ok(VcpuRunState::Runnable)
412
}
413
414
/// Makes unrunnable VCPUs return immediately from `wait_until_runnable`.
415
/// For WhpxSplitIrqChip this is a no-op because Whpx handles VCPU blocking.
416
fn kick_halted_vcpus(&self) {}
417
418
fn get_mp_state(&self, _vcpu_id: usize) -> Result<MPState> {
419
// WHPX does not seem to have an API for this, but luckily this API isn't used anywhere
420
// except the plugin.
421
Err(Error::new(libc::ENXIO))
422
}
423
424
fn set_mp_state(&mut self, _vcpu_id: usize, _state: &MPState) -> Result<()> {
425
// WHPX does not seem to have an API for this, but luckily this API isn't used anywhere
426
// except the plugin.
427
Err(Error::new(libc::ENXIO))
428
}
429
430
fn try_clone(&self) -> Result<Self>
431
where
432
Self: Sized,
433
{
434
Ok(WhpxSplitIrqChip {
435
vm: self.vm.try_clone()?,
436
routes: self.routes.clone(),
437
pit: self.pit.clone(),
438
pic: self.pic.clone(),
439
ioapic: self.ioapic.clone(),
440
ioapic_pins: self.ioapic_pins,
441
delayed_ioapic_irq_events: self.delayed_ioapic_irq_events.clone(),
442
irq_events: self.irq_events.clone(),
443
})
444
}
445
446
fn finalize_devices(
447
&mut self,
448
resources: &mut SystemAllocator,
449
io_bus: &Bus,
450
mmio_bus: &Bus,
451
) -> Result<()> {
452
// Insert pit into io_bus
453
io_bus.insert(self.pit.clone(), 0x040, 0x8).unwrap();
454
io_bus.insert(self.pit.clone(), 0x061, 0x1).unwrap();
455
456
// Insert pic into io_bus
457
io_bus.insert(self.pic.clone(), 0x20, 0x2).unwrap();
458
io_bus.insert(self.pic.clone(), 0xa0, 0x2).unwrap();
459
io_bus.insert(self.pic.clone(), 0x4d0, 0x2).unwrap();
460
461
// Insert ioapic into mmio_bus
462
mmio_bus
463
.insert(
464
self.ioapic.clone(),
465
IOAPIC_BASE_ADDRESS,
466
IOAPIC_MEM_LENGTH_BYTES,
467
)
468
.unwrap();
469
470
// At this point, all of our devices have been created and they have registered their
471
// irq events, so we can clone our resample events
472
let mut ioapic_resample_events: Vec<Vec<Event>> =
473
(0..self.ioapic_pins).map(|_| Vec::new()).collect();
474
let mut pic_resample_events: Vec<Vec<Event>> =
475
(0..self.ioapic_pins).map(|_| Vec::new()).collect();
476
477
for evt in self.irq_events.lock().iter().flatten() {
478
if (evt.gsi as usize) >= self.ioapic_pins {
479
continue;
480
}
481
if let Some(resample_evt) = &evt.resample_event {
482
ioapic_resample_events[evt.gsi as usize].push(resample_evt.try_clone()?);
483
pic_resample_events[evt.gsi as usize].push(resample_evt.try_clone()?);
484
}
485
}
486
487
// Register resample events with the ioapic
488
self.ioapic
489
.lock()
490
.register_resample_events(ioapic_resample_events);
491
// Register resample events with the pic
492
self.pic
493
.lock()
494
.register_resample_events(pic_resample_events);
495
496
// Make sure all future irq numbers are >= self.ioapic_pins
497
let mut irq_num = resources.allocate_irq().unwrap();
498
while irq_num < self.ioapic_pins as u32 {
499
irq_num = resources.allocate_irq().unwrap();
500
}
501
502
Ok(())
503
}
504
505
fn process_delayed_irq_events(&mut self) -> Result<()> {
506
let irq_events = self.irq_events.lock();
507
let mut delayed_events = self.delayed_ioapic_irq_events.lock();
508
delayed_events.events.retain(|&event_index| {
509
if let Some(evt) = &irq_events[event_index] {
510
if let Ok(mut ioapic) = self.ioapic.try_lock() {
511
if evt.resample_event.is_some() {
512
ioapic.service_irq(evt.gsi as usize, true);
513
} else {
514
ioapic.service_irq(evt.gsi as usize, true);
515
ioapic.service_irq(evt.gsi as usize, false);
516
}
517
518
false
519
} else {
520
true
521
}
522
} else {
523
true
524
}
525
});
526
527
if delayed_events.events.is_empty() {
528
delayed_events.trigger.wait()?;
529
}
530
Ok(())
531
}
532
533
fn irq_delayed_event_token(&self) -> Result<Option<Event>> {
534
Ok(Some(
535
self.delayed_ioapic_irq_events.lock().trigger.try_clone()?,
536
))
537
}
538
539
fn check_capability(&self, c: IrqChipCap) -> bool {
540
match c {
541
// It appears as though WHPX does not have tsc deadline support because we get guest
542
// MSR write failures if we enable it.
543
IrqChipCap::TscDeadlineTimer => false,
544
// TODO(b/180966070): Figure out how to query x2apic support.
545
IrqChipCap::X2Apic => false,
546
IrqChipCap::MpStateGetSet => false,
547
}
548
}
549
}
550
551
#[derive(Serialize, Deserialize)]
552
struct WhpxSplitIrqChipSnapshot {
553
routes: Vec<IrqRoute>,
554
}
555
556
impl IrqChipX86_64 for WhpxSplitIrqChip {
557
fn try_box_clone(&self) -> Result<Box<dyn IrqChipX86_64>> {
558
Ok(Box::new(self.try_clone()?))
559
}
560
561
fn as_irq_chip(&self) -> &dyn IrqChip {
562
self
563
}
564
565
fn as_irq_chip_mut(&mut self) -> &mut dyn IrqChip {
566
self
567
}
568
569
/// Get the current state of the PIC
570
fn get_pic_state(&self, select: PicSelect) -> Result<PicState> {
571
Ok(self.pic.lock().get_pic_state(select))
572
}
573
574
/// Set the current state of the PIC
575
fn set_pic_state(&mut self, select: PicSelect, state: &PicState) -> Result<()> {
576
self.pic.lock().set_pic_state(select, state);
577
Ok(())
578
}
579
580
/// Get the current state of the IOAPIC
581
fn get_ioapic_state(&self) -> Result<IoapicState> {
582
Ok(self.ioapic.lock().get_ioapic_state())
583
}
584
585
/// Set the current state of the IOAPIC
586
fn set_ioapic_state(&mut self, state: &IoapicState) -> Result<()> {
587
self.ioapic.lock().set_ioapic_state(state);
588
Ok(())
589
}
590
591
/// Get the current state of the specified VCPU's local APIC
592
fn get_lapic_state(&self, vcpu_id: usize) -> Result<LapicState> {
593
self.vm.get_vcpu_lapic_state(vcpu_id)
594
}
595
596
/// Set the current state of the specified VCPU's local APIC
597
fn set_lapic_state(&mut self, vcpu_id: usize, state: &LapicState) -> Result<()> {
598
self.vm.set_vcpu_lapic_state(vcpu_id, state)
599
}
600
601
fn lapic_frequency(&self) -> u32 {
602
WHPX_LOCAL_APIC_EMULATION_APIC_FREQUENCY
603
}
604
605
/// Retrieves the state of the PIT.
606
fn get_pit(&self) -> Result<PitState> {
607
Ok(self.pit.lock().get_pit_state())
608
}
609
610
/// Sets the state of the PIT.
611
fn set_pit(&mut self, state: &PitState) -> Result<()> {
612
self.pit.lock().set_pit_state(state);
613
Ok(())
614
}
615
616
/// Returns true if the PIT uses port 0x61 for the PC speaker, false if 0x61 is unused.
617
/// devices::Pit uses 0x61.
618
fn pit_uses_speaker_port(&self) -> bool {
619
true
620
}
621
622
fn snapshot_chip_specific(&self) -> anyhow::Result<AnySnapshot> {
623
AnySnapshot::to_any(&WhpxSplitIrqChipSnapshot {
624
routes: self.routes.lock().get_routes(),
625
})
626
.context("failed to snapshot WhpxSplitIrqChip")
627
}
628
629
fn restore_chip_specific(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
630
let mut deser: WhpxSplitIrqChipSnapshot =
631
AnySnapshot::from_any(data).context("failed to deserialize WhpxSplitIrqChip")?;
632
self.set_irq_routes(deser.routes.as_slice())?;
633
Ok(())
634
}
635
}
636
637