# How to Configure `VZVirtualMachineConfiguration` for iOS VMs: A Complete Guide

> Master VZVirtualMachineConfiguration for iOS VMs. Learn to set up PV=3 hardware, NVRAM, and essential devices like multi-touch and Secure Enclave for your vphone-cli project.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: how-to-guide
- Published: 2026-09-09

---

**`VZVirtualMachineConfiguration` for iOS VMs requires assembling a `VZMacPlatformConfiguration` with a PV=3 hardware model, persistent machine identifier, auxiliary NVRAM storage, and specialized devices including multi-touch, Secure Enclave, and synthetic battery support.**

Configuring Apple's Virtualization framework to run iOS requires precise setup that mirrors physical iPhone hardware. The open-source `vphone-cli` tool by Lakr233 demonstrates production-ready implementation in Swift, building a fully-featured iOS virtual machine through systematic `VZVirtualMachineConfiguration` assembly. This guide extracts the exact methodology from the source code, covering both minimal and complete configurations.

## Core Configuration Steps

The `vphone-cli` project constructs iOS VM configurations through 19 logical steps in [`VPhoneVirtualMachine.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachine.swift). Each step maps to specific framework APIs and private extensions.

### Step 1: Create the Hardware Model

All iOS VMs require a **PV=3 hardware model** generated through custom logic:

```swift
// VPhoneVirtualMachine.swift#L50-L52
let hwModel = try VPhoneHardware.createModel()

```

The `VPhoneHardware.createModel()` function in [`VPhoneHardware.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneHardware.swift) produces a `VZMacHardwareModel` compatible with iPhone virtualization. This hardware model defines the virtual device's capabilities and must match your target iOS version.

### Step 2: Configure Platform and NVRAM

The platform configuration binds hardware to persistent state:

```swift
// VPhoneVirtualMachine.swift#L106-L144
let platform = VZMacPlatformConfiguration()
platform.hardwareModel = hwModel
platform.machineIdentifier = resolvedIdentifier  // Persistent ECID/UDID
platform.auxiliaryStorage = try VZMacAuxiliaryStorage(
    creatingStorageAt: nvramURL,
    hardwareModel: hwModel,
    options: .allowOverwrite
)

```

**`VZMacAuxiliaryStorage`** maintains NVRAM variables across boots. The `machineIdentifier` must be persisted to `config.plist` via [`VPhoneVirtualMachineManifest.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachineManifest.swift) to ensure stable device identity.

### Step 3: Set Up the Boot Loader

```swift
// VPhoneVirtualMachine.swift#L146-L151
let bootLoader = VZMacOSBootLoader()
if let romURL = options.romURL {
    Dynamic(bootLoader)._setROMURL(romURL)  // Private API for custom ROM
}
config.bootLoader = bootLoader

```

The `VZMacOSBootLoader` initiates the iOS kernel. Custom ROM injection requires private API access through the `Dynamic` wrapper.

### Step 4: Configure CPU and Memory

Framework minimums must be respected with explicit clamping:

```swift
// VPhoneVirtualMachine.swift#L155-L159
config.cpuCount = max(requestedCPUs, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
config.memorySize = max(requestedMemory, VZVirtualMachineConfiguration.minimumAllowedMemorySize)

```

For iOS, typical production values are **4+ CPU cores** and **4GB+ RAM**, though these scale with host capabilities.

## Essential Device Configurations

Beyond core settings, iOS VMs require specialized device configurations unavailable in standard macOS virtualization.

### Graphics Display Configuration

Match iPhone screen parameters precisely:

```swift
// VPhoneVirtualMachine.swift#L61-L69
let graphics = VZMacGraphicsDeviceConfiguration()
graphics.displays = [
    VZMacGraphicsDisplayConfiguration(
        widthInPixels: 1290,   // iPhone 15 Pro width
        heightInPixels: 2796,  // iPhone 15 Pro height
        pixelsPerInch: 460     // Retina density
    )
]
config.graphicsDevices = [graphics]

```

### Audio Input/Output Streams

```swift
// VPhoneVirtualMachine.swift#L71-L78
let audio = VZVirtioSoundDeviceConfiguration()
let inputStream = VZVirtioSoundDeviceInputStreamConfiguration()
inputStream.source = VZHostAudioInputStreamSource()
let outputStream = VZVirtioSoundDeviceOutputStreamConfiguration()
outputStream.sink = VZHostAudioOutputStreamSink()
audio.streams = [inputStream, outputStream]
config.audioDevices = [audio]

```

### Storage Attachment

```swift
// VPhoneVirtualMachine.swift#L80-L86
let attachment = try VZDiskImageStorageDeviceAttachment(
    url: diskImageURL,
    readOnly: false
)
let storage = VZVirtioBlockDeviceConfiguration(attachment: attachment)
config.storageDevices = [storage]

```

### Network Device

Network configuration delegates to [`VPhoneNetworking.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneNetworking.swift):

```swift
// VPhoneVirtualMachine.swift#L88-L92
if let networkDevice = try VPhoneNetworking.makeNetworkDevice(networkConfig) {
    config.networkDevices = [networkDevice]
}

```

This supports **NAT**, **bridged**, or **none** modes based on manifest settings.

### Serial Console (PL011 UART)

Interactive debugging requires serial port wiring to host pipes:

```swift
// VPhoneVirtualMachine.swift#L94-L122
guard let serialPort = Dynamic._VZPL011SerialPortConfiguration().asObject 
    as? VZSerialPortConfiguration else {
    throw VPhoneError.serialPortUnavailable
}

let inputPipe = Pipe()
let outputPipe = Pipe()
serialPort.attachment = VZFileHandleSerialPortAttachment(
    fileHandleForReading: inputPipe.fileHandleForReading,
    fileHandleForWriting: outputPipe.fileHandleForWriting
)

// Host stdin → VM input
// VM output → host stdout (read from outputPipe elsewhere)
config.serialPorts = [serialPort]

```

## Private API Extensions for iOS Support

Standard `Virtualization` framework APIs lack several iOS-critical features. `vphone-cli` accesses these through private API bridging.

### Hardware Accelerators

```swift
// VPhoneVirtualMachine.swift#L124-L130
if let videoToolbox = Dynamic._VZMacVideoToolboxDeviceConfiguration().asObject,
   let neuralEngine = Dynamic._VZMacNeuralEngineDeviceConfiguration().asObject,
   let scaler = Dynamic._VZMacScalerAcceleratorDeviceConfiguration().asObject {
    Dynamic(config)._setAcceleratorDevices([videoToolbox, neuralEngine, scaler])
}

```

**`VZMacVideoToolboxDeviceConfiguration`**, **`VZMacNeuralEngineDeviceConfiguration`**, and **`VZMacScalerAcceleratorDeviceConfiguration`** enable Media Engine, Neural Engine, and display scaler passthrough respectively.

### Multi-Touch Input

Critical for iOS UI interaction:

```swift
// VPhoneVirtualMachine.swift#L132-L136
if let touchScreen = Dynamic._VZUSBTouchScreenConfiguration().asObject {
    Dynamic(config)._setMultiTouchDevices([touchScreen])
}

```

**`VZUSBTouchScreenConfiguration`** exposes touch events to the guest system.

### Synthetic Battery

iOS requires valid battery state for proper operation:

```swift
// VPhoneVirtualMachine.swift#L150-L158
let batterySource = Dynamic._VZMacSyntheticBatterySource()
batterySource.setCharge(100.0)      // Full charge
batterySource.setConnectivity(1)     // Charging state

let batteryConfig = Dynamic._VZMacBatteryPowerSourceDeviceConfiguration()
batteryConfig.setSource(batterySource.asObject)

if let battery = batteryConfig.asObject {
    Dynamic(config)._setPowerSourceDevices([battery])
}

```

### GDB Debug Stub

Kernel debugging support:

```swift
// VPhoneVirtualMachine.swift#L160-L176
let debugPort = options.kernelDebugPort ?? 0  // 0 = auto-assign
guard debugPort == 0 || (6000...65535).contains(debugPort) else {
    throw VPhoneError.invalidKernelDebugPort(debugPort)
}

let debugStub = Dynamic._VZGDBDebugStubConfiguration(port: debugPort)
Dynamic(config)._setDebugStub(debugStub.asObject)

```

### Secure Enclave Processor (SEP)

iOS security features require SEP emulation:

```swift
// VPhoneVirtualMachine.swift#L178-L188
let sep = Dynamic._VZSEPCoprocessorConfiguration(storageURL: sepStorageURL)
if let sepRom = options.sepRomURL {
    sep.setRomBinaryURL(sepRom)
}
sep.setDebugStub(Dynamic._VZGDBDebugStubConfiguration().asObject)

if let sepConfig = sep.asObject {
    Dynamic(config)._setCoprocessors([sepConfig])
}

```

## Complete Implementation Examples

### Minimal Working Configuration

This stripped-down example boots iOS with essential components only:

```swift
import Virtualization

func createMinimaliOSConfig(
    diskURL: URL,
    nvramURL: URL,
    hardwareModel: VZMacHardwareModel
) throws -> VZVirtualMachineConfiguration {
    
    // Platform with persistent storage
    let platform = VZMacPlatformConfiguration()
    platform.hardwareModel = hardwareModel
    platform.auxiliaryStorage = try VZMacAuxiliaryStorage(
        creatingStorageAt: nvramURL,
        hardwareModel: hardwareModel,
        options: .allowOverwrite
    )
    
    // Core configuration
    let config = VZVirtualMachineConfiguration()
    config.bootLoader = VZMacOSBootLoader()
    config.platform = platform
    config.cpuCount = max(4, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
    config.memorySize = max(4_294_967_296, VZVirtualMachineConfiguration.minimumAllowedMemorySize)
    
    // Display
    let graphics = VZMacGraphicsDeviceConfiguration()
    graphics.displays = [
        VZMacGraphicsDisplayConfiguration(
            widthInPixels: 1290,
            heightInPixels: 2796,
            pixelsPerInch: 460
        )
    ]
    config.graphicsDevices = [graphics]
    
    // Storage
    let storage = try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false)
    config.storageDevices = [
        VZVirtioBlockDeviceConfiguration(attachment: storage)
    ]
    
    // Validation
    try config.validate()
    return config
}

```

### Production-Grade Configuration (Full `vphone-cli` Implementation)

```swift
import Dynamic
import Virtualization

struct VMOptions {
    let cpuCount: Int
    let memorySize: UInt64
    let screenWidth: Int
    let screenHeight: Int
    let screenPPI: Int
    let diskURL: URL
    let nvramURL: URL
    let romURL: URL?
    let sepStorageURL: URL
    let sepRomURL: URL?
    let networkConfig: NetworkConfiguration?
    let kernelDebugPort: UInt16?
    let noVphoned: Bool
}

func createProductioniOSConfig(options: VMOptions) throws -> VZVirtualMachineConfiguration {
    
    // 1. Hardware model
    let hwModel = try VPhoneHardware.createModel()
    
    // 2. Platform setup
    let platform = VZMacPlatformConfiguration()
    platform.hardwareModel = hwModel
    platform.auxiliaryStorage = try VZMacAuxiliaryStorage(
        creatingStorageAt: options.nvramURL,
        hardwareModel: hwModel,
        options: .allowOverwrite
    )
    
    // 3. Boot loader with optional ROM
    let bootLoader = VZMacOSBootLoader()
    if let rom = options.romURL {
        Dynamic(bootLoader)._setROMURL(rom)
    }
    
    // 4. Base configuration
    let config = VZVirtualMachineConfiguration()
    config.bootLoader = bootLoader
    config.platform = platform
    config.cpuCount = max(options.cpuCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
    config.memorySize = max(options.memorySize, VZVirtualMachineConfiguration.minimumAllowedMemorySize)
    
    // 5. Graphics
    let gfx = VZMacGraphicsDeviceConfiguration()
    gfx.displays = [
        VZMacGraphicsDisplayConfiguration(
            widthInPixels: options.screenWidth,
            heightInPixels: options.screenHeight,
            pixelsPerInch: options.screenPPI
        )
    ]
    config.graphicsDevices = [gfx]
    
    // 6. Audio
    let audio = VZVirtioSoundDeviceConfiguration()
    audio.streams = [
        VZVirtioSoundDeviceInputStreamConfiguration().with { $0.source = VZHostAudioInputStreamSource() },
        VZVirtioSoundDeviceOutputStreamConfiguration().with { $0.sink = VZHostAudioOutputStreamSink() }
    ]
    config.audioDevices = [audio]
    
    // 7. Storage
    let disk = try VZDiskImageStorageDeviceAttachment(url: options.diskURL, readOnly: false)
    config.storageDevices = [VZVirtioBlockDeviceConfiguration(attachment: disk)]
    
    // 8. Network
    if let net = try VPhoneNetworking.makeNetworkDevice(options.networkConfig) {
        config.networkDevices = [net]
    }
    
    // 9. Serial console
    if let serial = Dynamic._VZPL011SerialPortConfiguration().asObject as? VZSerialPortConfiguration {
        let (inPipe, outPipe) = (Pipe(), Pipe())
        serial.attachment = VZFileHandleSerialPortAttachment(
            fileHandleForReading: inPipe.fileHandleForReading,
            fileHandleForWriting: outPipe.fileHandleForWriting
        )
        config.serialPorts = [serial]
    }
    
    // 10. Accelerators (Video Toolbox, Neural Engine, Scaler)
    if let vt = Dynamic._VZMacVideoToolboxDeviceConfiguration().asObject,
       let ne = Dynamic._VZMacNeuralEngineDeviceConfiguration().asObject,
       let sc = Dynamic._VZMacScalerAcceleratorDeviceConfiguration().asObject {
        Dynamic(config)._setAcceleratorDevices([vt, ne, sc])
    }
    
    // 11. Touch screen
    if let touch = Dynamic._VZUSBTouchScreenConfiguration().asObject {
        Dynamic(config)._setMultiTouchDevices([touch])
    }
    
    // 12. Entropy
    config.entropyDevices = [VZVirtioEntropyDeviceConfiguration()]
    
    // 13. Keyboard
    config.keyboards = [VZUSBKeyboardConfiguration()]
    
    // 14. Socket device (vsock)
    if !options.noVphoned {
        config.socketDevices = [VZVirtioSocketDeviceConfiguration()]
    }
    
    // 15. Battery
    let battery = Dynamic._VZMacSyntheticBatterySource()
    battery.setCharge(100.0)
    battery.setConnectivity(1)
    let batteryDev = Dynamic._VZMacBatteryPowerSourceDeviceConfiguration()
    batteryDev.setSource(battery.asObject)
    if let b = batteryDev.asObject {
        Dynamic(config)._setPowerSourceDevices([b])
    }
    
    // 16. Debug stub
    let debugPort = options.kernelDebugPort.map(Int.init) ?? 0
    let debugStub = Dynamic._VZGDBDebugStubConfiguration(port: debugPort)
    Dynamic(config)._setDebugStub(debugStub.asObject)
    
    // 17. SEP coprocessor
    let sep = Dynamic._VZSEPCoprocessorConfiguration(storageURL: options.sepStorageURL)
    if let sepRom = options.sepRomURL { sep.setRomBinaryURL(sepRom) }
    sep.setDebugStub(Dynamic._VZGDBDebugStubConfiguration().asObject)
    if let s = sep.asObject {
        Dynamic(config)._setCoprocessors([s])
    }
    
    // 18. Validation
    try config.validate()
    return config
}

```

## Validation and Instantiation

Always validate before creating the VM instance:

```swift
// VPhoneVirtualMachine.swift#L190-L193
try config.validate()

let virtualMachine = VZVirtualMachine(configuration: config)
virtualMachine.delegate = self

```

`config.validate()` throws `VZError` if any configuration violates framework constraints—common failures include incompatible hardware models, insufficient resources, or missing required devices.

## Key Source Files Reference

| File | Purpose | Lines of Interest |
|------|---------|-----------------|
| [`VPhoneVirtualMachine.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachine.swift) | Central VM construction | 50–193 (full configuration pipeline) |
| [`VPhoneHardware.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneHardware.swift) | PV=3 hardware model generation | Entire file |
| [`VPhoneVirtualMachineManifest.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachineManifest.swift) | `config.plist` persistence | Machine identifier storage |
| [`VPhoneNetworking.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneNetworking.swift) | Virtual NIC construction | Network device factory |
| [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) | vsock host-guest channel | Socket device client |

## Summary

- **Hardware model**: Use `VPhoneHardware.createModel()` for PV=3 iPhone compatibility
- **Platform setup**: Combine `VZMacPlatformConfiguration` with `VZMacAuxiliaryStorage` for persistent NVRAM
- **Core resources**: Clamp CPU and memory to `VZVirtualMachineConfiguration` minimums
- **iOS-specific devices**: Require private API access for touch screen, accelerators, battery, and SEP
- **Validation**: Always call `config.validate()` before `VZVirtualMachine` instantiation
- **Serial debugging**: Wire `VZPL011SerialPortConfiguration` to host pipes for interactive console access

## Frequently Asked Questions

### What is PV=3 in iOS virtualization?

PV=3 refers to **Platform Version 3**, the hardware model identifier Apple uses for modern iPhone SoCs (A15 and later). `vphone-cli` generates this through `VPhoneHardware.createModel()` in [`VPhoneHardware.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneHardware.swift). The hardware model determines which iOS kernels will boot and what device features are exposed to the guest.

### Why does iOS VM configuration require private APIs?

Apple's public `Virtualization` framework only exposes macOS virtualization APIs. iOS-specific features—**multi-touch input** (`VZUSBTouchScreenConfiguration`), **Secure Enclave** (`VZSEPCoprocessorConfiguration`), **battery simulation** (`VZMacSyntheticBatterySource`), and **media accelerators**—are implemented in framework binaries but not public headers. `vphone-cli` accesses these through runtime message sending via the `Dynamic` wrapper.

### How do I persist VM state across reboots?

Three components require persistence: (1) the **disk image** (standard file), (2) **auxiliary storage** (`VZMacAuxiliaryStorage` pointing to an NVRAM file), and (3) **machine identifier** (ECID/UDID stored in `config.plist` via [`VPhoneVirtualMachineManifest.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachineManifest.swift)). The manifest file must be read at launch and updated after any identifier changes.

### What network modes does `vphone-cli` support?

According to [`VPhoneNetworking.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneNetworking.swift), the tool supports **NAT** (default, isolated with outbound access), **bridged** (direct interface attachment), and **none** (disconnected). The mode is determined by parsing `config.plist` and constructing the appropriate `VZNetworkDeviceConfiguration` subclass—typically `VZNATNetworkDeviceConfiguration` or `VZBridgedNetworkDeviceConfiguration`.