summaryrefslogtreecommitdiffstats
path: root/scripts/run-qemu-vm.sh
blob: 9ca902310562053557ec23154f699319bc53146a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
# SPDX-License-Identifier: MIT
#
# run-qemu-vm.sh — Launch a QEMU VM from OE build artifacts
#
# Finds the native QEMU binary, kernel, and rootfs from the build
# directory and launches a VM with optional socket networking for
# multi-node testing.
#
# Usage:
#   # Single VM (slirp network only)
#   ./run-qemu-vm.sh
#
#   # Server VM for multi-node (socket listen)
#   ./run-qemu-vm.sh --role server --socket-port 1234
#
#   # Agent VM for multi-node (socket connect)
#   ./run-qemu-vm.sh --role agent --socket-port 1234
#
#   # Custom settings
#   ./run-qemu-vm.sh --build-dir /path/to/build --machine qemuarm64 \
#                     --image container-image-host --memory 4096
#
# Environment variables (override defaults):
#   BUILD_DIR       Build directory (default: auto-detect from poky)
#   MACHINE         Target machine (default: qemux86-64)
#   IMAGE           Image name (default: container-image-host)
#   VM_MEMORY       VM memory in MB (default: 4096)
#   NO_KVM          Set to 1 to disable KVM (default: use KVM if available)

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAYER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

# Defaults
MACHINE="${MACHINE:-qemux86-64}"
IMAGE="${IMAGE:-container-image-host}"
VM_MEMORY="${VM_MEMORY:-4096}"
ROLE=""
SOCKET_PORT=""
ROOTFS_PATH=""
NO_KVM="${NO_KVM:-0}"
EXTRA_CMDLINE=""

usage() {
    sed -n '/^# Usage:/,/^[^#]/p' "$0" | grep '^#' | sed 's/^# \?//'
    echo ""
    echo "Options:"
    echo "  --build-dir DIR     Build directory (default: auto-detect)"
    echo "  --machine MACHINE   Target machine (default: qemux86-64)"
    echo "  --image IMAGE       Image name (default: container-image-host)"
    echo "  --memory MB         VM memory in MB (default: 4096)"
    echo "  --role ROLE         'server' (socket listen) or 'agent' (socket connect)"
    echo "  --socket-port PORT  Socket port for multi-node networking"
    echo "  --rootfs PATH       Override rootfs path (e.g., for agent copy)"
    echo "  --append ARGS       Extra kernel cmdline arguments"
    echo "  --no-kvm            Disable KVM acceleration"
    echo "  --help              Show this help"
    exit 0
}

# Parse arguments
while [ $# -gt 0 ]; do
    case "$1" in
        --build-dir)    BUILD_DIR="$2"; shift 2 ;;
        --machine)      MACHINE="$2"; shift 2 ;;
        --image)        IMAGE="$2"; shift 2 ;;
        --memory)       VM_MEMORY="$2"; shift 2 ;;
        --role)         ROLE="$2"; shift 2 ;;
        --socket-port)  SOCKET_PORT="$2"; shift 2 ;;
        --rootfs)       ROOTFS_PATH="$2"; shift 2 ;;
        --append)       EXTRA_CMDLINE="$2"; shift 2 ;;
        --no-kvm)       NO_KVM=1; shift ;;
        --help|-h)      usage ;;
        *)              echo "Unknown option: $1"; usage ;;
    esac
done

# Auto-detect build directory
if [ -z "$BUILD_DIR" ]; then
    # Walk up from layer to find poky, then build
    POKY_DIR="$(cd "$LAYER_DIR/.." && pwd)"
    if [ -d "$POKY_DIR/build" ]; then
        BUILD_DIR="$POKY_DIR/build"
    else
        echo "ERROR: Cannot auto-detect build directory. Use --build-dir."
        exit 1
    fi
fi

DEPLOY_DIR="$BUILD_DIR/tmp/deploy/images/$MACHINE"

# Architecture-specific settings
case "$MACHINE" in
    qemux86-64)
        QEMU_BIN="qemu-system-x86_64"
        QEMU_MACHINE="-M q35"
        QEMU_CPU_KVM="-cpu host"
        QEMU_CPU_TCG="-cpu Skylake-Client"
        KERNEL_NAME="bzImage"
        CONSOLE="ttyS0"
        ROOTDEV="/dev/vda"
        ;;
    qemuarm64)
        QEMU_BIN="qemu-system-aarch64"
        QEMU_MACHINE="-M virt"
        QEMU_CPU_KVM="-cpu host"
        QEMU_CPU_TCG="-cpu cortex-a57"
        KERNEL_NAME="Image"
        CONSOLE="ttyAMA0"
        ROOTDEV="/dev/vda"
        ;;
    *)
        echo "ERROR: Unsupported machine '$MACHINE'. Supported: qemux86-64, qemuarm64"
        exit 1
        ;;
esac

# Find native QEMU binary
NATIVE_BINDIR="$BUILD_DIR/tmp/sysroots-components/x86_64/qemu-system-native/usr/bin"
if [ -x "$NATIVE_BINDIR/$QEMU_BIN" ]; then
    QEMU_PATH="$NATIVE_BINDIR/$QEMU_BIN"
elif command -v "$QEMU_BIN" >/dev/null 2>&1; then
    QEMU_PATH="$(command -v "$QEMU_BIN")"
else
    echo "ERROR: $QEMU_BIN not found in $NATIVE_BINDIR or PATH"
    exit 1
fi

# Set LD_LIBRARY_PATH for native QEMU shared libraries
NATIVE_LIBDIRS=""
for libdir in "$BUILD_DIR"/tmp/sysroots-components/x86_64/*/usr/lib; do
    [ -d "$libdir" ] && NATIVE_LIBDIRS="${NATIVE_LIBDIRS:+$NATIVE_LIBDIRS:}$libdir"
done
export LD_LIBRARY_PATH="${NATIVE_LIBDIRS}:${LD_LIBRARY_PATH:-}"

# Find kernel
KERNEL="$DEPLOY_DIR/$KERNEL_NAME"
if [ ! -f "$KERNEL" ]; then
    echo "ERROR: Kernel not found: $KERNEL"
    exit 1
fi

# Find or use provided rootfs
if [ -n "$ROOTFS_PATH" ]; then
    ROOTFS="$ROOTFS_PATH"
else
    ROOTFS=$(ls -t "$DEPLOY_DIR/${IMAGE}-${MACHINE}".rootfs.ext4 2>/dev/null | head -1)
    if [ -z "$ROOTFS" ]; then
        echo "ERROR: No ext4 rootfs found for $IMAGE in $DEPLOY_DIR"
        exit 1
    fi
fi

if [ ! -f "$ROOTFS" ]; then
    echo "ERROR: Rootfs not found: $ROOTFS"
    exit 1
fi

# KVM detection
KVM_FLAG=""
QEMU_CPU="$QEMU_CPU_TCG"
if [ "$NO_KVM" != "1" ]; then
    HOST_ARCH=$(uname -m)
    case "$MACHINE" in
        qemux86-64) TARGET_ARCH="x86_64" ;;
        qemuarm64)  TARGET_ARCH="aarch64" ;;
    esac
    if [ "$HOST_ARCH" = "$TARGET_ARCH" ] && [ -w /dev/kvm ]; then
        KVM_FLAG="-enable-kvm"
        QEMU_CPU="$QEMU_CPU_KVM"
        echo "KVM acceleration enabled"
    else
        echo "KVM not available, using TCG emulation"
    fi
fi

# Build socket networking args
SOCKET_ARGS=""
if [ -n "$ROLE" ] && [ -n "$SOCKET_PORT" ]; then
    case "$ROLE" in
        server)
            SOCKET_ARGS="-netdev socket,id=vlan0,listen=:${SOCKET_PORT} -device virtio-net-pci,netdev=vlan0"
            echo "Socket networking: listening on port $SOCKET_PORT (server)"
            ;;
        agent)
            SOCKET_ARGS="-netdev socket,id=vlan0,connect=127.0.0.1:${SOCKET_PORT} -device virtio-net-pci,netdev=vlan0"
            echo "Socket networking: connecting to port $SOCKET_PORT (agent)"
            ;;
        *)
            echo "ERROR: --role must be 'server' or 'agent'"
            exit 1
            ;;
    esac
fi

echo "QEMU:    $QEMU_PATH"
echo "Kernel:  $KERNEL"
echo "Rootfs:  $ROOTFS"
echo "Machine: $MACHINE"
echo "Memory:  ${VM_MEMORY}M"
echo ""

exec "$QEMU_PATH" \
    $QEMU_MACHINE $QEMU_CPU $KVM_FLAG \
    -m "$VM_MEMORY" -smp 2 -nographic \
    -kernel "$KERNEL" \
    -drive "file=$ROOTFS,if=virtio,format=raw" \
    -append "root=$ROOTDEV rw console=$CONSOLE ip=dhcp${EXTRA_CMDLINE:+ $EXTRA_CMDLINE}" \
    -netdev user,id=net0 -device virtio-net-pci,netdev=net0 \
    $SOCKET_ARGS