How to Configure the Serial Port for the vphone‑cli Virtual iPhone VM
The vphone‑cli VM exposes a PL011 UART connected to the host via Unix pipes, with stdin forwarded to the guest's serial input and guest output streamed to stdout for an interactive console.
The vphone‑cli tool provisions virtualized iPhone environments using Apple's Virtualization framework. Serial port configuration is essential for capturing boot logs, debugging firmware, and maintaining interactive shell access. This guide explains how the serial port is implemented in the source code and how to customize it for your workflow.
How the Serial Port Is Implemented
The serial subsystem is defined in sources/vphone-cli/VPhoneVirtualMachine.swift. According to the Lakr233/vphone‑cli source code, the implementation combines NVRAM boot arguments, a PL011 UART configuration, and bidirectional pipe-based I/O.
NVRAM Boot Arguments Enable UART Output
The VM's NVRAM is configured with specific boot arguments that initialize the PL011 UART at firmware level:
// VPhoneVirtualMachine.swift, lines 136-144
let bootArgs = "serial=3 debug=0x104c04"
// ...
nvram.variable(bootVariable, value: bootArgs.data(using: .utf8)!)
serial=3— Activates UART unit 3 for serial outputdebug=0x104c04— Sets the debug mask enabling verbose boot logging
These values are written to NVRAM during VM creation and persist across launches.
PL011 Serial Port Configuration
The VM instantiates a PL011 UART through Apple's private Virtualization API:
// VPhoneVirtualMachine.swift, lines 94-100
let serialPort = Dynamic._VZPL011SerialPortConfiguration()
serialPort.attachment = Dynamic._VZFileHandleCentralizedPipeAttachment(
inputPipe: inputPipe,
outputPipe: outputPipe
)
The Dynamic._VZPL011SerialPortConfiguration() wrapper accesses private framework methods required for ARM64 guest support.
Bidirectional Pipe Architecture
Two Pipe objects handle data flow:
| Pipe | Direction | Implementation |
|---|---|---|
| Input pipe | Host stdin → VM serial input |
Lines 106-116: Background thread reads FileHandle.standardInput and writes to pipe |
| Output pipe | VM serial output → Host stdout |
Lines 118-124: Read handle stored in serialOutputReadHandle |
The serial port is attached to the VM configuration at lines 120-121:
config.serialPorts = [serialPort]
print("[vphone] PL011 serial port attached (interactive)")
Output Forwarding at Runtime
When the VM launches, a readability handler forwards guest output to the terminal:
// VPhoneVirtualMachine.swift, lines 300-306
serialOutputReadHandle?.readabilityHandler = { handle in
let data = handle.availableData
if !data.isEmpty {
FileHandle.standardOutput.write(data)
}
}
Serial Port Configuration Options
Enable or Disable Serial Output
Serial attachment is automatic when the PL011 class is available. To disable:
// Comment out lines 94-121 in VPhoneVirtualMachine.swift
// Remove or comment: config.serialPorts = [serialPort]
Note that sources/vphone-cli/VPhoneCreateOrchestrator.swift intentionally disables the serial console for non-interactive commands like vm create.
Redirect Output to a Log File
Replace stdout forwarding with file-based logging:
let logURL = URL(fileURLWithPath: "/tmp/vphone_serial.log")
FileManager.default.createFile(atPath: logURL.path, contents: nil, attributes: nil)
let logHandle = try FileHandle(forWritingTo: logURL)
if let readHandle = serialOutputReadHandle {
readHandle.readabilityHandler = { handle in
let data = handle.availableData
if !data.isEmpty {
logHandle.write(data) // Persist to file instead of terminal
}
}
}
Modify Boot Arguments
Adjust the UART unit or debug verbosity by editing lines 136-138:
// Use UART unit 0 instead of 3
let bootArgs = "serial=0 debug=0x104c04"
// Reduce debug output
let bootArgs = "serial=3 debug=0x100000"
Valid serial values are 0 through 3, corresponding to different UART instances in the iPhone firmware.
Control Verbosity with CLI Flags
Pass -vv to any vm subcommand for enhanced serial logging:
./vphone vm launch -vv # Maximum serial output detail
./vphone vm create -vv # Verbose logging during VM provisioning
These flags are defined in sources/vphone-cli/VPhoneVMLaunchCLI.swift (lines 19-20).
Practical Usage Examples
Launch with Interactive Console (Default Behavior)
make boot # GUI launch with terminal serial console
./vphone vm launch # CLI launch with serial attached
Capture Boot Logs to File
- Apply the log file modification shown above
- Rebuild:
swift build - Launch:
./vphone vm launch - Review:
tail -f /tmp/vphone_serial.log
Run Headless Without Serial
For automated testing or CI environments:
// VPhoneVirtualMachine.swift: omit serial port configuration
// Ensure no config.serialPorts assignment
Then launch with:
./vphone vm launch --no-gui # if available, or rely on default headless modes
Summary
- PL011 UART is the hardware interface, configured via
Dynamic._VZPL011SerialPortConfiguration()inVPhoneVirtualMachine.swift - Boot arguments (
serial=3 debug=0x104c04) activate firmware-level serial output through NVRAM variables - Unix pipes provide the host-guest transport mechanism, with
stdin/stdoutas defaults - Customization involves modifying pipe endpoints, boot argument strings, or commenting out the serial attachment block entirely
- Verbosity control uses the
-vvCLI flag defined inVPhoneVMLaunchCLI.swift
Frequently Asked Questions
How do I change which UART unit the vphone‑cli VM uses?
Edit the bootArgs string at lines 136-138 in VPhoneVirtualMachine.swift. Replace serial=3 with serial=0, serial=1, or serial=2 to select a different UART instance. Rebuild the project and recreate the VM for changes to take effect.
Can I capture serial output without displaying it in the terminal?
Yes. Replace the readabilityHandler assignment in VPhoneVirtualMachine.swift (around line 300) with a custom handler that writes to a FileHandle connected to your target file, socket, or logging service. The pipe-based architecture allows any FileHandle-compatible destination.
Why does vm create not show serial output while vm launch does?
VPhoneCreateOrchestrator.swift intentionally omits serial console setup for creation operations, as these are typically non-interactive and automated. Only vm launch and similar runtime commands attach the PL011 port by default. For debugging creation issues, temporarily enable serial in the orchestrator or use vm launch with a pre-created VM.
Where are the firmware-level serial patches documented?
The research/iboot_patches.md file in the repository contains documentation on the boot-time serial initialization and the meaning of debug mask values like 0x104c04. This is essential reading for low-level firmware debugging.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →