# How to Configure the Serial Port for the vphone‑cli Virtual iPhone VM

> Configure the serial port for your vphone-cli VM easily. Learn how to connect your host and guest via Unix pipes for an interactive console experience.

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

---

**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`](https://github.com/Lakr233/vphone-cli/blob/main/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:

```swift
// 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 output
- **`debug=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:

```swift
// 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:

```swift
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:

```swift
// 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:

```swift
// Comment out lines 94-121 in VPhoneVirtualMachine.swift
// Remove or comment: config.serialPorts = [serialPort]

```

Note that [`sources/vphone-cli/VPhoneCreateOrchestrator.swift`](https://github.com/Lakr233/vphone-cli/blob/main/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:

```swift
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:

```swift
// 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:

```bash
./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`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneVMLaunchCLI.swift) (lines 19-20).

## Practical Usage Examples

### Launch with Interactive Console (Default Behavior)

```bash
make boot              # GUI launch with terminal serial console

./vphone vm launch     # CLI launch with serial attached

```

### Capture Boot Logs to File

1. Apply the log file modification shown above
2. Rebuild: `swift build`
3. Launch: `./vphone vm launch`
4. Review: `tail -f /tmp/vphone_serial.log`

### Run Headless Without Serial

For automated testing or CI environments:

```swift
// VPhoneVirtualMachine.swift: omit serial port configuration
// Ensure no config.serialPorts assignment

```

Then launch with:

```bash
./vphone vm launch --no-gui  # if available, or rely on default headless modes

```

## Summary

- **PL011 UART** is the hardware interface, configured via `Dynamic._VZPL011SerialPortConfiguration()` in [`VPhoneVirtualMachine.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachine.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`/`stdout` as defaults
- **Customization** involves modifying pipe endpoints, boot argument strings, or commenting out the serial attachment block entirely
- **Verbosity control** uses the `-vv` CLI flag defined in [`VPhoneVMLaunchCLI.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVMLaunchCLI.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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/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.