Skip to content

Commit d33011c

Browse files
committed
fix: Call KVM_KVMCLOCK_CTRL not only after pause but also before resume
KVM_KVMCLOCK_CTRL ioctl sets `pvclock_set_guest_stopped_request` flag of `kvm_vcpu_arch` [1]. On the next guest time update, if the flag is set, KVM ORs in `PVCLOCK_GUEST_STOPPED` and `kvm_setup_guest_pvclock()` pushes the `hv_clock` into the guest's pvclock page [2]. If the `hv_clock` has not been written to the guest's pvclock page when taking a snapshot, it is not saved in the snapshot memory (i.e. `PVCLOCK_GUEST_STOPPED` isn't set in resumed VMs). So we should call KVM_KVMCLOCK_CTRL ioctl before resuming a VM in addition to after pausing a VM. [1]: https://elixir.bootlin.com/linux/v6.16.3/source/arch/x86/kvm/x86.c#L5734 [2]: https://elixir.bootlin.com/linux/v6.16.3/source/arch/x86/kvm/x86.c#L3286-L3295 Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent cc20162 commit d33011c

File tree

3 files changed

+20
-18
lines changed

3 files changed

+20
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ and this project adheres to
4343
bug causing a read/write from an iovec to be duplicated when receiving an
4444
error on an iovec other than the first. This caused a data corruption issue in
4545
the vsock device starting from guest kernel 6.17.
46+
- [#5494](https://github.com/firecracker-microvm/firecracker/pull/5494): Fixed a
47+
watchdog soft lockup bug on microVMs restored from snapshots by calling
48+
KVM_KVMCLOCK_CTRL ioctl before resuming.
4649

4750
## [1.13.0]
4851

src/vmm/src/arch/x86_64/vcpu.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,19 @@ impl KvmVcpu {
272272
self.peripherals.pio_bus = Some(pio_bus);
273273
}
274274

275+
/// Calls KVM_KVMCLOCK_CTRL to avoid guest soft lockup watchdog panics on resume.
276+
/// See https://docs.kernel.org/virt/kvm/api.html .
277+
pub fn kvmclock_ctrl(&self) {
278+
// We do not want to fail if the call is not successful, because that may be acceptable
279+
// depending on the workload. For example, EINVAL is returned if kvm-clock is not
280+
// activated (e.g., no-kvmclock is specified in the guest kernel parameter).
281+
// https://elixir.bootlin.com/linux/v6.17.5/source/arch/x86/kvm/x86.c#L5736-L5737
282+
if let Err(err) = self.fd.kvmclock_ctrl() {
283+
METRICS.vcpu.kvmclock_ctrl_fails.inc();
284+
warn!("KVM_KVMCLOCK_CTRL call failed {}", err);
285+
}
286+
}
287+
275288
/// Get the current XSAVE state for this vCPU.
276289
///
277290
/// The C `kvm_xsave` struct was extended by adding a flexible array member (FAM) in the end
@@ -699,6 +712,8 @@ impl KvmVcpu {
699712
self.fd
700713
.set_vcpu_events(&state.vcpu_events)
701714
.map_err(KvmVcpuError::VcpuSetVcpuEvents)?;
715+
716+
self.kvmclock_ctrl();
702717
Ok(())
703718
}
704719
}

src/vmm/src/vstate/vcpu.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -234,16 +234,8 @@ impl Vcpu {
234234
// If the emulation requests a pause lets do this
235235
#[cfg(feature = "gdb")]
236236
Ok(VcpuEmulation::Paused) => {
237-
// Calling `KVM_KVMCLOCK_CTRL` to make sure the guest softlockup watchdog
238-
// does not panic on resume, see https://docs.kernel.org/virt/kvm/api.html .
239-
// We do not want to fail if the call is not successful, because depending
240-
// that may be acceptable depending on the workload.
241237
#[cfg(target_arch = "x86_64")]
242-
if let Err(err) = self.kvm_vcpu.fd.kvmclock_ctrl() {
243-
METRICS.vcpu.kvmclock_ctrl_fails.inc();
244-
warn!("KVM_KVMCLOCK_CTRL call failed {}", err);
245-
}
246-
238+
self.kvm_vcpu.kvmclock_ctrl();
247239
return StateMachine::next(Self::paused);
248240
}
249241
// Emulation errors lead to vCPU exit.
@@ -263,15 +255,8 @@ impl Vcpu {
263255
.send(VcpuResponse::Paused)
264256
.expect("vcpu channel unexpectedly closed");
265257

266-
// Calling `KVM_KVMCLOCK_CTRL` to make sure the guest softlockup watchdog
267-
// does not panic on resume, see https://docs.kernel.org/virt/kvm/api.html .
268-
// We do not want to fail if the call is not successful, because depending
269-
// that may be acceptable depending on the workload.
270258
#[cfg(target_arch = "x86_64")]
271-
if let Err(err) = self.kvm_vcpu.fd.kvmclock_ctrl() {
272-
METRICS.vcpu.kvmclock_ctrl_fails.inc();
273-
warn!("KVM_KVMCLOCK_CTRL call failed {}", err);
274-
}
259+
self.kvm_vcpu.kvmclock_ctrl();
275260

276261
// Move to 'paused' state.
277262
state = StateMachine::next(Self::paused);
@@ -322,7 +307,6 @@ impl Vcpu {
322307
);
323308
self.kvm_vcpu.fd.set_kvm_immediate_exit(0);
324309
}
325-
// Nothing special to do.
326310
self.response_sender
327311
.send(VcpuResponse::Resumed)
328312
.expect("vcpu channel unexpectedly closed");

0 commit comments

Comments
 (0)