HVF virtual counter diverges from CLOCK_VIRTUAL when the host sleeps

Host environment

  • Operating system: macOS
  • OS/kernel version: Darwin aki-MacBook-Air.local 21.3.0 Darwin Kernel Version 21.3.0: Wed Jan 5 21:37:58 PST 2022; root:xnu-8019.80.24~20/RELEASE_ARM64_T8101 arm64
  • Architecture: arm64
  • QEMU flavor: qemu-system-aarch64
  • QEMU version: 6621441d
  • QEMU command line:
    ./qemu-system-aarch64 -machine virt,accel=hvf,highmem=off -cpu cortex-a72 -drive if=pflash,file=edk2-aarch64-code.fd -drive if=pflash,file=edk2-arm-vars.fd -drive if=virtio,file=fedora.raw

Emulated/Virtualized environment

  • Operating system: Fedora 35
  • OS/kernel version: Linux fedora-qemu 5.15.17-200.fc35.aarch64 #1 SMP Thu Jan 27 16:12:44 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
  • Architecture: aarch64

Description of problem

HVF's virtual counter diverges from CLOCK_VIRTUAL when the host sleeps and causes the inconsistency between Linux's system counter and everything else.

HVF's virtual counter apparently relies on something similar to mach_absolute_time, which stops when the host sleeps and resumes after it wakes up. However, CLOCK_VIRTUAL is implemented with mach_continuous_time, which continues even while the host sleeps. Linux uses the virtual counter as the source of the system counter and sees inconsistencies between the system counter and the other devices.

Steps to reproduce

  1. Launch Fedora.
  2. Compare the time shown at the top of the guest display and one at the top of the host display. The difference should be less than 2 minutes.
  3. Let the host sleep for 3 minutes.
  4. Compare the times again. The difference is now greater than 2 minutes.

Additional information

Here are solutions I've came up with so far. There are trade-offs but any of them should be better than the current situation. I'm happy to implement one if the maintainers have decided which one is the best or figure out a superior alternative.

  • Implement cpus_get_virtual_clock of AccelOpsClass with mach_absolute_time. It would make HVF inconsistent with the other accelerators. Linux also expects the virtual clock is "continuous" and it leaves the divergence from the real time.
  • Request XNU HOST_NOTIFY_CALENDAR_CHANGE to update the virtual clock with the continuous time. The interface is undocumented.
  • Use IORegisterForSystemPower to update the virtual clock with the continuous time. It is undocumented that the interface handles every cases where mach_absolute_time and mach_continuous_time, but it actually does if I read XNU's source code correctly.