1#!/usr/bin/env bash
2
3# Copyright 2016 The Fuchsia Authors
4#
5# Use of this source code is governed by a MIT-style
6# license that can be found in the LICENSE file or at
7# https://opensource.org/licenses/MIT
8
9function HELP {
10    echo "help:"
11    echo "-a <arch>            : arm64, or x64"
12    echo "-b                   : build first"
13    echo "-c <text>            : add item to kernel commandline"
14    echo "-C                   : use Clang build"
15    echo "-A                   : use ASan build"
16    echo "-P                   : use profile build"
17    echo "-L                   : use LTO build"
18    echo "-l                   : use ThinLTO build"
19    echo "-d                   : run with emulated disk"
20    echo "-D <disk file|device>: specify disk file or device path on host, default is blk.bin"
21    echo "--disktype[=<type>]  : should be one of (ahci, virtio, nvme), default is ahci"
22    echo "--diskfmt[=<format>] : disk format (raw, qcow2, etc), default is raw"
23    echo "-g                   : use graphical console"
24    echo "-G <version>         : use GIC v2 or v3"
25    echo "-I <interface name>  : network interface name, default is qemu."
26    echo "-k                   : use KVM"
27    echo "-m <memory in MB>    : memory size, default is ${MEMSIZE_DEFAULT}MB"
28    echo "-n                   : run with emulated nic"
29    echo "-N                   : run with emulated nic via tun/tap"
30    echo "-o <dir>             : build directory"
31    echo "-q <directory>       : location of qemu, defaults to looking in prebuilt/downloads/qemu/bin, then \$PATH"
32    echo "-r                   : run release build"
33    echo "-s <number of cpus>  : number of cpus, 1 for uniprocessor, default is 4"
34    echo "-t <binary>          : use <binary> as the QEMU->ZBI trampoline"
35    echo "-u <path>            : execute qemu startUp script, default is no script"
36    echo "-V                   : try to use virtio devices"
37    echo "-z <zbi>             : boot specified complete ZBI via trampoline"
38    echo "--audio[=<host_drv>] : use Intel HD Audio"
39    echo "                     : <host_drv> should be one of (alsa, pa, wav, none)"
40    echo "--ahci=<disk image>  : run with disk image file as raw ahci drive"
41    echo "--build-debug        : build an image for use with gdb (equivalent to -d to build-zircon)"
42    echo "--debugger           : Enable gdb stub and wait for connection"
43    echo "--no-serial          : Disable writing out to the guest's serial port"
44    echo "--vnc=<display>      : use vnc based display"
45    echo "--wavfile=<file>     : When audio host_drv == wav, output to the specified WAV file"
46    echo "-h for help"
47    echo "all arguments after -- are passed to qemu directly"
48    exit 1
49}
50
51DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
52
53AHCI=()
54ARCH=
55ASAN=0
56AUDIO=
57AUDIO_WAVFILE="/tmp/qemu.wav"
58BUILD=0
59CLANG=0
60DEBUGGER=0
61BUILD_DEBUG=0
62DISK=0
63DISKFILE="blk.bin"
64DISKTYPE=
65DISKFMT="raw"
66BUILDDIR=
67GIC=3
68GRAPHICS=0
69DO_KVM=0
70LTO=0
71THINLTO=0
72PROFILE=0
73MEMSIZE_DEFAULT=2048
74MEMSIZE=$MEMSIZE_DEFAULT
75NET=0
76QEMUDIR=
77RELEASE=0
78UPSCRIPT=no
79VNC=
80VIRTIO=0
81SERIAL=1
82SMP=4
83CMDLINE=""
84QEMU_KERNEL=
85QEMU_INITRD=
86
87if [[ "$(uname -s)" == "Darwin" ]]; then
88  IFNAME="tap0"
89else
90  IFNAME="qemu"
91fi
92
93# QEMU looks for its own files in its current directory before looking in its
94# data directory (.../share/qemu/).  So a file in the current directory that
95# happens to match one of those internal files' names will be used instead of
96# the proper file and make things go awry.  There's no way to tell QEMU not to
97# look in the current directory first.  So to make it safe to have files by any
98# name in the current directory, we cd to / before running QEMU (on the more
99# reasonable presumption that / won't contain any files by those names).  Hence,
100# we have to convert any relative file names we're passing to QEMU to absolute.
101abspath() {
102  local path="$1"
103  case "$path" in
104      /*) echo "$path";;
105      *) echo "`pwd`/$path";;
106  esac
107}
108
109while getopts "Aa:bc:CdD:gG:I:kLlm:nNo:Pq:rs:t::u:Vz:h-:" FLAG; do
110    case $FLAG in
111        A) ASAN=1;;
112        a) ARCH=$OPTARG;;
113        b) BUILD=1;;
114        c) CMDLINE+="$OPTARG ";;
115        C) CLANG=1;;
116        d) DISK=1;;
117        D) DISKFILE="$(abspath "$OPTARG")";;
118        g) GRAPHICS=1;;
119        G) GIC=$OPTARG;;
120        I) IFNAME=$OPTARG;;
121        k) DO_KVM=1;;
122        L) LTO=1;;
123        l) THINLTO=1;;
124        m) MEMSIZE=$OPTARG;;
125        n) NET=1;;
126        N) NET=2;;
127        o) BUILDDIR=$OPTARG;;
128        P) PROFILE=1;;
129        q) QEMUDIR=${OPTARG}/;;
130        r) RELEASE=1;;
131        s) SMP=$OPTARG;;
132        t) QEMU_KERNEL="$(abspath "$OPTARG")";;
133        u) UPSCRIPT="$(abspath "$OPTARG")";;
134        V) VIRTIO=1;;
135        z) QEMU_INITRD="$(abspath "$OPTARG")";;
136        h) HELP;;
137        \?)
138            echo unrecognized option
139            HELP
140            ;;
141        -)
142            case $OPTARG in
143            ahci=*) AHCI+=("$(abspath "${OPTARG#*=}")");;
144            audio) AUDIO=none;;
145            audio=*) AUDIO=${OPTARG#*=};;
146            wavfile=*) AUDIO_WAVFILE="$(abspath "${OPTARG#*=}")";;
147            build-debug) BUILD_DEBUG=1;;
148            debugger) DEBUGGER=1;;
149            disktype=*) DISKTYPE=${OPTARG#*=};;
150            diskfmt=*) DISKFMT=${OPTARG#*=};;
151            no-serial) SERIAL=0;;
152            vnc=*) VNC=${OPTARG#*=};;
153            *)
154                echo unrecognized long option
155                HELP
156                ;;
157            esac
158            ;;
159    esac
160done
161shift $((OPTIND-1))
162
163# arch argument is non optional
164if [[ -z $ARCH ]]; then
165    echo must specify arch
166    HELP
167fi
168
169PROJECT="$ARCH"
170
171BUILDDIR_SUFFIX=
172BUILD_ARGS=
173
174if (( $ASAN )); then
175    BUILDDIR_SUFFIX+=-asan
176    BUILD_ARGS+=' -A'
177elif (( $LTO )); then
178    BUILDDIR_SUFFIX+=-lto
179    BUILD_ARGS+=' -L'
180elif (( $THINLTO )); then
181    BUILDDIR_SUFFIX+=-thinlto
182    BUILD_ARGS+=' -l'
183elif (( $PROFILE )); then
184    BUILDDIR_SUFFIX+=-profile
185    BUILD_ARGS+=' -P'
186elif (( $CLANG )); then
187    BUILDDIR_SUFFIX+=-clang
188    BUILD_ARGS+=' -C'
189fi
190
191if (( $RELEASE )); then
192    BUILDDIR_SUFFIX+=-release
193    BUILD_ARGS+=' -r'
194fi
195
196if (( $BUILD_DEBUG )); then
197    BUILD=1
198    BUILD_ARGS+=' -d'
199fi
200
201# build the project if asked for
202if (( $BUILD )); then
203    # DIR is zircon/scripts, we need to make inside zircon.
204    $DIR/build-zircon -a $ARCH $BUILD_ARGS -- -C "$DIR/.." || exit 1
205fi
206
207# by default use the qemu binary located in the fuchsia buildtools
208# repo if we can find it, but allow -q to override it for people
209# who want to use their own.
210case "$(uname -s)" in
211  Darwin)
212    readonly HOST_PLATFORM="mac-x64"
213    ;;
214  Linux)
215    readonly HOST_PLATFORM="linux-x64"
216    ;;
217esac
218
219if [[ -z $QEMUDIR && -d "$DIR/../prebuilt/downloads/qemu/bin" ]]; then
220    QEMUDIR="$DIR/../prebuilt/downloads/qemu/bin/"
221fi
222
223if [[ -z $BUILDDIR ]]; then
224  BUILDDIR="$(dirname "$DIR")/build-$PROJECT$BUILDDIR_SUFFIX"
225fi
226
227if [[ -z "$QEMU_INITRD" ]]; then
228    QEMU_INITRD="$(abspath "$BUILDDIR/zircon.zbi")"
229fi
230
231if [[ -z "$QEMU_KERNEL" ]]; then
232    case $ARCH in
233    arm64) QEMU_KERNEL="$(abspath "$BUILDDIR/qemu-boot-shim.bin")" ;;
234    x64) QEMU_KERNEL="$(abspath "$BUILDDIR/multiboot.bin")" ;;
235    *)
236        echo >&2 "No QEMU trampoline for $ARCH"
237        exit 2
238        ;;
239    esac
240fi
241
242if (( $BUILD )); then
243    if [[ -n "$QEMU_INITRD" && "$QEMU_INITRD" != $BUILDDIR/* ]]; then
244        echo >&2 "WARNING: ZBI $QEMU_INITRD not in $BUILDDIR just built"
245    fi
246    if [[ -n "$QEMU_KERNEL" && "$QEMU_KERNEL" != $BUILDDIR/* ]]; then
247        echo >&2 "WARNING: trampoline $QEMU_KERNEL not in $BUILDDIR just built"
248    fi
249fi
250
251# construct the args for qemu
252ARGS=" -m $MEMSIZE"
253if [[ -n $VNC ]]; then
254    ARGS+=" -vnc $VNC"
255fi
256
257if (( !$GRAPHICS  )); then
258    ARGS+=" -nographic"
259else
260    ARGS+=" -serial stdio"
261    if [[ "$ARCH" == "x64" && $VIRTIO == 0 ]]; then
262        # Enable Bochs VBE device, which Zircon has a device for
263        ARGS+=" -vga std"
264    else
265        # use the virtio gpu for display
266        ARGS+=" -vga none"
267        ARGS+=" -device virtio-gpu-pci"
268    fi
269fi
270
271if (( $DISK )); then
272    # if disktype wasn't set on the command line, default to ahci unless VIRTIO is set
273    if [[ -z $DISKTYPE ]]; then
274        if (( $VIRTIO )); then
275            DISKTYPE="virtio"
276        else
277            DISKTYPE="ahci"
278        fi
279    fi
280
281    ARGS+=" -drive file=${DISKFILE},format=${DISKFMT},if=none,id=mydisk"
282    if [[ "$DISKTYPE" == "virtio" ]]; then
283        ARGS+=" -device virtio-blk-pci,drive=mydisk"
284    elif [[ "$DISKTYPE" == "ahci" ]]; then
285        ARGS+=" -device ich9-ahci,id=ahci -device ide-drive,drive=mydisk,bus=ahci.0"
286    elif [[ "$DISKTYPE" == "nvme" ]]; then
287        ARGS+=" -device nvme,drive=mydisk,serial=zircon"
288    else
289        echo unrecognized disk type \"$DISKTYPE\"
290        exit
291    fi
292fi
293
294ahcinum=1
295for ahcifile in ${AHCI[@]}; do
296    ARGS+=" -drive file=${ahcifile},format=raw,if=none,id=ahcidisk${ahcinum}"
297    ARGS+=" -device ich9-ahci,id=ahci${ahcinum}"
298    ARGS+=" -device ide-drive,drive=ahcidisk${ahcinum},bus=ahci.${ahcinum}"
299    ahcinum=$((ahcinum + 1))
300done
301
302if (( !$NET )); then
303  ARGS+=" -net none"
304fi
305
306if [[ $NET == 1 ]]; then
307    ARGS+=" -netdev type=user,hostname=$IFNAME,id=net0"
308fi
309
310if [[ $NET == 2 ]]; then
311    if [[ "$(uname -s)" == "Darwin" ]]; then
312        if [[ ! -c "/dev/$IFNAME" ]]; then
313          echo "To use qemu with networking on macOS, install the tun/tap driver:"
314          echo "http://tuntaposx.sourceforge.net/download.xhtml"
315          exit 1
316        fi
317        if [[ ! -w "/dev/$IFNAME" ]]; then
318          echo "For networking /dev/$IFNAME must be owned by $USER. Please run:"
319          echo "  sudo chown $USER /dev/$IFNAME"
320          exit 1
321        fi
322        ARGS+=" -netdev type=tap,ifname=$IFNAME,script=$UPSCRIPT,downscript=no,id=net0"
323    else
324        CHECK=$(tunctl -b -u $USER -t $IFNAME 2>/dev/null)
325        if [[ "$CHECK" != "$IFNAME" ]]; then
326          echo "To use qemu with networking on Linux, configure tun/tap:"
327          if [[ ! -x "/usr/sbin/tunctl" ]]; then
328            echo "sudo apt-get install uml-utilities"
329          fi
330          echo "sudo tunctl -u $USER -t $IFNAME"
331          echo "sudo ifconfig $IFNAME up"
332          exit 1
333        fi
334        ARGS+=" -netdev type=tap,ifname=$IFNAME,script=$UPSCRIPT,downscript=no,id=net0"
335    fi
336fi
337
338if (( $NET )); then
339    MAC=""
340    if [[ $NET == 2 ]]; then
341        HASH=$(echo $IFNAME | shasum)
342        SUFFIX=$(for i in {0..2}; do echo -n :${HASH:$(( 2 * $i )):2}; done)
343        MAC=",mac=52:54:00$SUFFIX"
344    fi
345    if [[ "$ARCH" == "x64" ]] && [[ $VIRTIO == 0 ]]; then
346        ARGS+=" -device e1000,netdev=net0${MAC}"
347    else
348        ARGS+=" -device virtio-net-pci,netdev=net0${MAC}"
349    fi
350fi
351
352if [[ -n $AUDIO ]]; then
353    ARGS+=" -soundhw hda"
354    export QEMU_AUDIO_DRV=$AUDIO
355    export QEMU_AUDIO_DAC_FIXED_FREQ=48000
356    export QEMU_AUDIO_TIMER_PERIOD=20
357
358    case $AUDIO in
359        none) ;;
360        alsa) ;;
361        pa) ;;
362        wav)
363            export QEMU_WAV_FREQUENCY=48000
364            export QEMU_WAV_PATH=${AUDIO_WAVFILE}
365            ;;
366        *)
367            echo unrecognized QEMU host audio driver \"$AUDIO\"
368            exit
369            ;;
370    esac
371fi
372
373if [[ $SMP != 1 ]]; then
374    ARGS+=" -smp $SMP"
375    if [[ "$ARCH" == "x64" ]]; then
376        ARGS+=",threads=2"
377    fi
378fi
379
380# start a few extra harmless virtio devices that can be ignored
381if (( $VIRTIO )); then
382    ARGS+=" -device virtio-serial-pci"
383    ARGS+=" -device virtio-rng-pci"
384    ARGS+=" -device virtio-mouse-pci"
385    ARGS+=" -device virtio-keyboard-pci"
386fi
387
388if (( $DEBUGGER )); then
389    ARGS+=" -s -S"
390fi
391
392case $ARCH in
393    arm64)
394        QEMU=${QEMUDIR}qemu-system-aarch64
395        if (( $DO_KVM )); then
396          ARGS+=" -enable-kvm -cpu host"
397          GIC=host
398        else
399          ARGS+=" -machine virtualization=true -cpu cortex-a53"
400        fi
401        ARGS+=" -machine virt"
402        # append a gic version to the machine specifier
403        if [[ $GIC != 0 ]]; then
404            ARGS+=",gic_version=${GIC}"
405        fi
406
407        if (( !$SERIAL )); then
408          CMDLINE+="kernel.serial=none "
409        fi
410        ;;
411    x64)
412        QEMU=${QEMUDIR}qemu-system-x86_64
413        ARGS+=" -machine q35"
414        ARGS+=" -device isa-debug-exit,iobase=0xf4,iosize=0x04"
415        if (( $DO_KVM )); then
416          ARGS+=" -enable-kvm -cpu host,migratable=no,+invtsc"
417        else
418          ARGS+=" -cpu Haswell,+smap,-check,-fsgsbase"
419        fi
420
421        if (( $SERIAL )); then
422          CMDLINE+="kernel.serial=legacy "
423        else
424          CMDLINE+="kernel.serial=none "
425        fi
426        ;;
427    *)
428        echo unsupported arch
429        HELP
430        ;;
431esac
432
433# Propagate our TERM environment variable as a kernel command line
434# argument.  This is last so that an explicit -c TERM=foo argument
435# goes into CMDLINE first.  Kernel command line words become environment
436# variables, and the first variable in the list wins for getenv calls.
437if [[ -n $TERM ]]; then
438    CMDLINE+="TERM=$TERM "
439fi
440
441# Add entropy to the kernel
442CMDLINE+="kernel.entropy-mixin=$(head -c 32 /dev/urandom | shasum -a 256 | awk '{ print $1 }') "
443
444# Don't 'reboot' the emulator if the kernel crashes
445CMDLINE+="kernel.halt-on-panic=true "
446
447CMDLINE="`echo "$CMDLINE" | sed 's/,/,,/g'`"
448
449# run qemu
450echo CMDLINE: $CMDLINE
451cd /
452set -x
453exec $QEMU -kernel "$QEMU_KERNEL" -initrd "$QEMU_INITRD" \
454     	   $ARGS -append "$CMDLINE" "$@"
455