Virtualization.framework - Request stop?

Hi! I'm trying to figure out what mechanism request stop sends to the guest to actually request a stop. It doesn't appear that Virtualization.framework implements any ACPI bits relating to power buttons, so unclear how a linux VM would detect that a request has been stopped. I don't see any documentation around what devices are implemented by Virtualization.framework either, in terms of things like realtime clock, etc.

Thanks for any help!

It depends if the virtual machine is using Apple Silicon or an Intel CPU.

On Apple Silicon Macs, the shutdown is signaled through the PL061. In order to use it, the kernel needs to be compiled with that device (CONFIG_GPIO_PL061). From user space, a process like input-event-daemon can turn the event in an action.

On Intel Macs, the stop request is done through the ICH9. The PM1_STS’s Power Button Status (PWRBTN_STS) is set in response to the request.

Following the guidance in this thread about using PL061 for graceful shutdown on Apple Silicon, I've configured a Linux guest with all the recommended settings, but the GPIO never actually changes when requestStopWithError() is called.

Kernel configuration:

  • CONFIG_GPIO_PL061=y
  • CONFIG_KEYBOARD_GPIO=y (gpio-keys driver)
  • CONFIG_INPUT=y, CONFIG_INPUT_EVDEV=y

What's working:

  • PL061 driver loads: pl061_gpio 20060000.pl061: PL061 GPIO chip registered
  • gpio-keys creates input device: /dev/input/event0
  • Interrupt registered: 17: 0 20060000.pl061 6 Edge GPIO Key Power
  • Init process monitors /dev/input/event* for KEY_POWER events
  • API returns success: canRequestStop() = true, requestStopWithError() returns Ok

What I observed:

Using devmem to read PL061 registers directly before and after calling requestStopWithError():

GPIODATA (0x200603FC): 0x00000000  ← GPIO stays LOW
GPIOIBE  (0x20060408): 0x00000040  ← Pin 6: both edges enabled
GPIOIE   (0x20060410): 0x00000040  ← Pin 6: interrupt enabled
GPIORIS  (0x20060414): 0x00000000  ← No interrupt pending

The interrupt is configured for both edges (GPIOIBE bit 6 = 1), so even a brief pulse should trigger it. But:

  • GPIO value never changes from 0
  • Interrupt count in /proc/interrupts stays at 0
  • No KEY_POWER input event is generated

Environment:

  • macOS 15.2 (Sequoia) on Apple Silicon (M3)
  • Virtualization.framework via Swift/ObjC bindings
  • Linux kernel 6.12 with minimal config
  • Direct boot (no UEFI)

Additional observation:

I noticed that Apple's own containerization project doesn't appear to use requestStopWithError() for Linux VMs. In VZVirtualMachine+Helpers.swift, only the force stop() method is wrapped, and their vminitd agent uses signalProcess() to send signals to guest processes rather than relying on the GPIO power button mechanism.

Questions:

  1. Is there additional VZ configuration needed for requestStopWithError() to signal the GPIO on ARM64 Linux guests?
  2. Is this mechanism expected to work with Linux guests, or is an agent-based approach (like in the containerization project) the recommended pattern?
  3. Is there something about the device tree or gpio-keys configuration that needs to be different?

Any guidance would be appreciated. Happy to provide additional details or test specific configurations.

Virtualization.framework - Request stop?
 
 
Q