blob: 18cfefc456766bc3c8c083ce015e85a3099fc42b [file] [log] [blame]
#!/usr/bin/env bash
# Copyright 2016 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT
function HELP {
echo "help:"
echo "-a <arch> : arm64, riscv64, or x64"
echo "-c <text> : append item to kernel commandline"
echo "-D <disk file|device>: specify disk file or device path on host"
echo "--disktype[=<type>] : should be one of (ahci, virtio, nvme, virtio-scsi, usb), default is ahci"
echo "--diskfmt[=<format>] : disk format (raw, qcow2, etc), default is raw"
echo "-g : use graphical console"
echo "-H, --hvf : use HVF (macOS hosts only); defaults to true if supported."
echo " --no-hvf : don't use HVF, even if supported."
echo "-I <interface name> : network interface name, default is qemu."
echo "-k, --kvm : use KVM (Linux hosts only); defaults to true if supported."
echo " --no-kvm : don't use KVM, even if supported."
echo "-m <memory in MB> : memory size, default is ${MEMSIZE_DEFAULT}MB"
echo "-n : run with emulated nic"
echo "-N : run with emulated nic via tun/tap"
echo "-q <directory> : location of qemu, defaults to looking in prebuilt/downloads/qemu/bin, then \$PATH"
echo "-s <number of cpus> : number of cpus, 1 for uniprocessor, default is $SMP_DEFAULT"
echo "-t <binary> : use <binary> as the QEMU->ZBI trampoline"
echo "-u <path> : execute qemu startUp script, default is no script"
echo "-V, --virtio : use virtio devices; defaults to true."
echo " --no-virtio : don't use virtio devices."
echo "-z <zbi> : boot specified ZBI via trampoline"
echo "--audio[=<host_drv>] : use Intel HD Audio"
echo " <host_drv> should be one of (alsa, oss, pa, wav, none)"
echo "--ahci=<disk image> : run with disk image file as raw ahci drive"
echo "--debugger : Enable gdb stub and wait for connection"
echo "--dry-run : do everything but start qemu"
echo "--gic=<version> : use GIC 2, 3, or max supported. default is max"
echo "--no-serial : Disable writing out to the guest's serial port"
echo "-S <device> : output serial port to a QEMU character device. Default is stdio."
echo "-M <device> : output QEMU monitor console to a QEMU character device"
echo "--uefi : Boot QEMU through UEFI. With this setting, one of -t or -D must be"
echo " supplied: if -t is supplied, then that value is assumed to be a"
echo " UEFI executable; else, the value under -D is assumbed to be a "
echo " bootable, UEFI FAT filesystem or disk image. In the second case,"
echo " if --disktype is unset, the image is specified as the contents of"
echo " a USB disk drive. -z may be passed alongside -t to specify a"
echo " ramdisk that the UEFI boot application should in turn boot;"
echo " however, no UEFI such boot shims are yet supported."
echo "--vnc=<display> : use vnc based display"
echo "--wavfile=<file> : When audio host_drv == wav, output to the specified WAV file"
echo "-h for help"
echo "all arguments after -- are passed to qemu directly"
exit 1
}
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Note these match the defaults in [virtual device specs](/build/images/flash/BUILD.gn;l=517)
# and https://source.corp.google.com/fuchsia/build/sdk/virtual_device.gni
# (//src/developer/ffx/plugins/emulator/src/vdl_files.rs).This can be overriden by the emulator
# configs in the infra/recipe repo
# (https://fuchsia.googlesource.com/infra/recipes/+/refs/heads/main/recipe_modules/emu/api.py).
readonly MEMSIZE_DEFAULT=8192
readonly SMP_DEFAULT=4
# Host operating system. Will either be "Linux" or "Darwin" (macOS) on
# supported platforms.
readonly HOST_OS=$(uname -s)
# Determine host architecture.
case "$(uname -m)" in
aarch64*|arm64)
readonly HOST_ARCH="arm64"
;;
x86_64)
readonly HOST_ARCH="x64"
;;
*)
echo "unknown host architecture: $(uname -m)"
exit 1
;;
esac
AHCI=()
ARCH=
AUDIO=
AUDIO_WAVFILE="/tmp/qemu.wav"
DEBUGGER=0
DISKFILE=
DISKTYPE=
DISKFMT="raw"
DRY_RUN=0
GIC=max
GRAPHICS=0
DO_HVF=
DO_KVM=
MEMSIZE=$MEMSIZE_DEFAULT
NET=0
QEMUDIR=
UEFI=0
UPSCRIPT=no
VNC=
VIRTIO=1
SERIAL=1
SERIAL_DEV="stdio"
MONITOR_DEV=
SMP=$SMP_DEFAULT
CMDLINE=""
OPT_CMDLINE=""
QEMU_KERNEL=
QEMU_INITRD=
if [[ $HOST_OS == "Darwin" ]]; then
IFNAME="tap0"
else
IFNAME="qemu"
fi
# Propagate our TERM environment variable as a kernel command line
# argument. This is first so that an explicit -c TERM=foo argument
# goes into CMDLINE later and overrides this.
if [[ -n $TERM ]]; then
CMDLINE+="TERM=$TERM "
fi
# QEMU looks for its own files in its current directory before looking in its
# data directory (.../share/qemu/). So a file in the current directory that
# happens to match one of those internal files' names will be used instead of
# the proper file and make things go awry. There's no way to tell QEMU not to
# look in the current directory first. So to make it safe to have files by any
# name in the current directory, we cd to / before running QEMU (on the more
# reasonable presumption that / won't contain any files by those names). Hence,
# we have to convert any relative file names we're passing to QEMU to absolute.
abspath() {
local path="$1"
case "$path" in
/*) echo "$path";;
*) echo "`pwd`/$path";;
esac
}
# The QEMU command-line arguments to be built up below.
ARGS=""
# The running number of certain disks by type.
AHCI_NUM=0
VIRTIO_SCSI_NUM=0
USB_NUM=0
#######################################
# Append disk-related arguments to ARGS.
#
# Globals:
# ARGS
# AHCI_NUM
# VIRTIO_SCSI_NUM
# USB_NUM
#
# Arguments:
# Disk type, a string.
# Disk file, a path.
# Disk format, a string.
# Disk ID, a string.
# Extra -device argunents, a string.
#######################################
function append-disk-args {
local type="$1"
local file="$2"
local fmt="$3"
local id="$4"
local extra_args="$5"
ARGS+=" -drive if=none,format=${fmt},file=${file},id=${id}"
if [[ "${type}" == "virtio" ]]; then
ARGS+=" -device virtio-blk-pci,drive=${id}"
elif [[ "${type}" == "ahci" ]]; then
if [[ $AHCI_NUM -eq 0 ]]; then
ARGS+=" -device ich9-ahci,id=ahci0"
fi
ARGS+=" -device ide-hd,drive=${id},bus=ahci0.${AHCI_NUM}"
AHCI_NUM=$((AHCI_NUM + 1))
elif [[ "${type}" == "nvme" ]]; then
ARGS+=" -device nvme,drive=${id},serial=zircon"
elif [[ "${type}" == "virtio-scsi" ]]; then
if [[ $VIRTIO_SCSI_NUM -eq 0 ]]; then
ARGS+=" -device virtio-scsi-pci,id=scsi0"
fi
ARGS+=" -device scsi-hd,drive=${id},scsi-id=${VIRTIO_SCSI_NUM},lun=1"
VIRTIO_SCSI_NUM=$((VIRTIO_SCSI_NUM + 1))
elif [[ "${type}" == "usb" ]]; then
if [[ $USB_NUM -eq 0 ]]; then
ARGS+=" -device nec-usb-xhci,id=xhci0"
fi
ARGS+=" -device usb-storage,bus=xhci0.0,removable=on,drive=${id}"
USB_NUM=$((USB_NUM + 1))
else
echo unrecognized disk type "${type}"
exit
fi
ARGS+="$extra_args"
}
while getopts "a:c:D:gHI:km:nNq:s:t::u:Vz:S:M:h-:" FLAG; do
case $FLAG in
a) ARCH=$OPTARG;;
c) OPT_CMDLINE+="$OPTARG ";;
D) DISKFILE="$(abspath "$OPTARG")";;
g) GRAPHICS=1;;
H)
if [[ "$HOST_OS" != "Darwin" ]]; then
echo "error: HVF option (-H) is only supported on MacOS"
exit 1
fi
if [[ "$HOST_ARCH" != "${ARCH}" ]]; then
echo "error: HVF option (-H) is only supported on a ${ARCH} host architecture"
exit 1
fi
if !$(sysctl -n kern.hv_support) ; then
echo "error: HVF is not supported"
fi
DO_HVF=1
;;
I) IFNAME=$OPTARG;;
k)
if [[ "$HOST_OS" != "Linux" ]]; then
echo "error: KVM option (-k) is only supported on Linux"
exit 1
fi
if [[ "$HOST_ARCH" != "${ARCH}" ]]; then
echo "error: KVM option (-k) is only supported on a ${ARCH} host architecture"
exit 1
fi
if [[ ! -w "/dev/kvm" ]]; then
echo "To use KVM acceleration, adjust permissions to /dev/kvm using:"
echo
echo "sudo chmod 666 /dev/kvm"
exit 1
fi
DO_KVM=1
;;
m) MEMSIZE=$OPTARG;;
n) NET=1;;
N) NET=2;;
q) QEMUDIR=${OPTARG}/;;
s) SMP=$OPTARG;;
t) QEMU_KERNEL="$(abspath "$OPTARG")";;
u) UPSCRIPT="$(abspath "$OPTARG")";;
V) VIRTIO=1;;
z) QEMU_INITRD="$(abspath "$OPTARG")";;
S) SERIAL_DEV=$OPTARG;;
M) MONITOR_DEV=$OPTARG;;
h) HELP;;
\?)
echo unrecognized option
HELP
;;
-)
case $OPTARG in
ahci=*) AHCI+=("$(abspath "${OPTARG#*=}")");;
audio) AUDIO=none;;
audio=*) AUDIO=${OPTARG#*=};;
wavfile=*) AUDIO_WAVFILE="$(abspath "${OPTARG#*=}")";;
debugger) DEBUGGER=1;;
disktype=*) DISKTYPE=${OPTARG#*=};;
diskfmt=*) DISKFMT=${OPTARG#*=};;
dry-run) DRY_RUN=1;;
gic=*) GIC=${OPTARG#*=};;
no-serial) SERIAL=0;;
vnc=*) VNC=${OPTARG#*=};;
no-kvm) DO_KVM=0;;
kvm) DO_KVM=1;;
no-hvf) DO_HVF=0;;
hvf) DO_HVF=1;;
no-virtio) VIRTIO=0;;
virtio) VIRTIO=1;;
uefi) UEFI=1;;
*)
echo unrecognized long option "$OPTARG"
HELP
;;
esac
;;
esac
done
shift $((OPTIND-1))
# arch argument is non optional
if [[ -z $ARCH ]]; then
echo must specify arch
HELP
fi
if [[ -z "$QEMU_KERNEL" ]]; then
if (( $UEFI )); then
if [[ -z "$DISKFILE" ]]; then
echo "with --uefi, one of -t or -D must be set"
HELP
fi
else
echo "-t switch is mandatory if --uefi is unset"
HELP
fi
if [[ -n "$OPT_CMDLINE" ]]; then
echo "-c cannot be set without -t"
HELP
fi
fi
if (( $UEFI )) && [[ -z "$QEMU_KERNEL" ]]; then
readonly UEFI_DISK_BOOT=1
else
readonly UEFI_DISK_BOOT=0
fi
# Fall back to disk type defaults if left unspecified with a provided emulated
# disk file.
if [[ -n "$DISKFILE" ]] && [[ -z "$DISKTYPE" ]]; then
if (( $VIRTIO )); then
DISKTYPE="virtio"
else
DISKTYPE="ahci"
fi
fi
# by default use the qemu binary located in the fuchsia //prebuilt
# repo if we can find it, but allow -q to override it for people
# who want to use their own.
if [[ -z $QEMUDIR && -d "$DIR/../prebuilt/downloads/qemu/bin" ]]; then
QEMUDIR="$DIR/../prebuilt/downloads/qemu/bin/"
fi
if (( $UEFI )); then
readonly EDK2DIR="$DIR/../../prebuilt/third_party/edk2/qemu-${ARCH}"
case $ARCH in
x64)
readonly UEFI_FIRMWARE="${EDK2DIR}/OVMF_CODE.fd"
readonly UEFI_VARS="${EDK2DIR}/OVMF_VARS.fd"
;;
arm64)
readonly UEFI_FIRMWARE="${EDK2DIR}/QEMU_EFI.fd"
readonly UEFI_VARS="${EDK2DIR}/QEMU_VARS.fd"
;;
esac
ARGS+=" -drive if=pflash,format=raw,readonly=on,file=${UEFI_FIRMWARE}"
# The UEFI variable store. Without 'snapshot=true' this file would need to be
# writable and state would persist across invocations; with it, the image
# will be copy-on-write and state should persist across reboots.
ARGS+=" -drive if=pflash,format=raw,snapshot=true,file=${UEFI_VARS}"
fi
if [[ -n "$QEMU_KERNEL" ]]; then
ARGS+=" -kernel $QEMU_KERNEL"
fi
if [[ -n "$QEMU_INITRD" ]]; then
ARGS+=" -initrd $QEMU_INITRD"
fi
if [[ -n $DISKFILE ]]; then
NAME="mydisk"
EXTRA_ARGS=""
if (( $UEFI_DISK_BOOT )); then
NAME="uefi"
# `bootindex=0` ensures that this is the highest priority boot option.
EXTRA_ARGS=",bootindex=0"
fi
append-disk-args "${DISKTYPE}" "${DISKFILE}" "${DISKFMT}" "${NAME}"
fi
# construct the args for qemu
ARGS+=" -m $MEMSIZE"
if [[ -n $VNC ]]; then
ARGS+=" -vnc $VNC"
fi
# Always use virtio as the rng source
ARGS+=" -device virtio-rng-pci"
if [[ -n "$MONITOR_DEV" ]]; then
ARGS+=" -monitor $MONITOR_DEV"
fi
ADD_SERIAL_ARG=1
if (( !$GRAPHICS )); then
ARGS+=" -nographic -vga none"
# -nographic implies -serial stdio and will complain if you
# try to set anything else pointing at stdio.
if [[ "$SERIAL_DEV" == "stdio" ]]; then
ADD_SERIAL_ARG=0
fi
else
if [[ "$ARCH" == "x64" && $VIRTIO == 0 ]]; then
# Enable Bochs VBE device, which Zircon has a device for
ARGS+=" -vga std"
else
# use the virtio gpu for display
ARGS+=" -vga none"
ARGS+=" -device virtio-gpu-pci"
fi
fi
if (( $SERIAL && $ADD_SERIAL_ARG )); then
ARGS+=" -serial $SERIAL_DEV"
fi
for ahcifile in ${AHCI[@]}; do
ARGS+=" -drive file=${ahcifile},format=raw,if=none,id=ahcidisk${AHCI_NUM}"
ARGS+=" -device ich9-ahci,id=ahci${AHCI_NUM}"
ARGS+=" -device ide-hd,drive=ahcidisk${AHCI_NUM},bus=ahci.${AHCI_NUM}"
AHCI_NUM=$((AHCI_NUM + 1))
done
if (( !$NET )); then
ARGS+=" -nic none"
else
if [[ $NET == 1 ]]; then
ARGS+=" -nic user,hostname=$IFNAME"
fi
if [[ $NET == 2 ]]; then
if [[ "$(uname -s)" == "Darwin" ]]; then
if [[ ! -c "/dev/$IFNAME" ]]; then
echo "To use qemu with networking on macOS, install the tun/tap driver:"
echo "http://tuntaposx.sourceforge.net/download.xhtml"
exit 1
fi
if [[ ! -w "/dev/$IFNAME" ]]; then
echo "For networking /dev/$IFNAME must be owned by $USER. Please run:"
echo " sudo chown $USER /dev/$IFNAME"
exit 1
fi
else
TAP_IFS=$(ip tuntap show 2>/dev/null)
if [[ ! "$TAP_IFS" =~ "${IFNAME}:" ]]; then
echo "To use qemu with networking on Linux, configure tun/tap:"
echo
echo "sudo ip tuntap add dev $IFNAME mode tap user $USER && \\"
echo "sudo ip link set $IFNAME up"
exit 1
fi
# Try to detect if a firewall is active. There are only few ways to do that
# without being root. Unfortunately, using systemd or systemctl does not work
# on Debian, so use the following hack instead:
if (which ufw && grep -q "^ENABLED=yes" /etc/ufw/ufw.conf) >/dev/null 2>&1; then
echo "Active firewall detected: If this emulator is unreachable, run: fx setup-ufw"
fi
fi
ARGS+=" -netdev tap,id=net0,ifname=$IFNAME,script=$UPSCRIPT,downscript=no"
fi
HASH=$(echo $IFNAME | shasum)
SUFFIX=$(for i in {0..2}; do echo -n :${HASH:$(( 2 * $i )):2}; done)
MAC="52:54:00$SUFFIX"
if [[ "$ARCH" == "x64" ]] && [[ $VIRTIO == 0 ]]; then
ARGS+=" -device e1000,netdev=net0,mac=$MAC"
else
# Disable iPXE romfile. OVMF (UEFI implementation) defaults to using iPXE to
# talk to the network device, but iPXE's driver is more than 2x slower than
# OVMF's own virtio-net driver.
ARGS+=" -device virtio-net-pci,romfile=,netdev=net0,vectors=8,mac=$MAC"
fi
fi
if [[ -n $AUDIO ]]; then
ARGS+=" -device intel-hda -device hda-duplex -audiodev id=${AUDIO},driver=${AUDIO}"
case $AUDIO in
none) ;;
alsa) ;;
oss) ;;
pa) ;;
wav)
ARGS+=",path=${AUDIO_WAVFILE}"
;;
*)
echo unrecognized QEMU host audio driver \"$AUDIO\"
exit
;;
esac
fi
if [[ $SMP != 1 ]]; then
ARGS+=" -smp $SMP"
fi
# start a few extra harmless virtio devices that can be ignored
if (( $VIRTIO )); then
ARGS+=" -device virtio-serial-pci"
ARGS+=" -device virtio-mouse-pci"
ARGS+=" -device virtio-keyboard-pci"
fi
if (( $DEBUGGER )); then
ARGS+=" -s -S"
fi
# Auto-detect if KVM or HVF support exists if not explicitly specified.
if [[ -z $DO_KVM ]]; then
if [[
$HOST_OS == "Linux" && # Linux only.
$HOST_ARCH == "${ARCH}" && # Host and target arch must match.
-w /dev/kvm # /dev/kvm must exist and be writable.
]]; then
echo "Enabling KVM acceleration: use '--no-kvm' to disable." >&2
DO_KVM=1
fi
fi
if [[ -z $DO_HVF ]]; then
if [[
$HOST_OS == "Darwin" && # macOS only.
$HOST_ARCH == "${ARCH}" && # Host and target arch must match.
$(sysctl -n kern.hv_support) == "1" # https://developer.apple.com/documentation/hypervisor
]]; then
echo "Enabling HVF acceleration: use '--no-hvf' to disable." >&2
DO_HVF=1
fi
fi
case $ARCH in
arm64)
QEMU=${QEMUDIR}qemu-system-aarch64
if (( $DO_KVM )); then
ARGS+=" -enable-kvm -cpu host"
GIC=host
elif (( $DO_HVF )); then
ARGS+=" -machine accel=hvf"
ARGS+=" -cpu host"
else
# Ask for all of the features TCG emulates
ARGS+=" -machine virtualization=true -cpu max"
fi
# Ask for a specific virt machine version.
# TODO(https://fxbug.dev/42131826) add support for high PCIe aperture
# Can switch to plain 'virt' once this is fixed.
ARGS+=" -machine virt-2.12"
# append a gic version to the machine specifier
if [[ $GIC != 0 ]]; then
ARGS+=",gic-version=${GIC}"
fi
if (( !$SERIAL )); then
CMDLINE+="kernel.serial=none "
fi
;;
riscv64)
QEMU=${QEMUDIR}qemu-system-riscv64
ARGS+=" -cpu rv64,svpbmt=true,v=true,vext_spec=v1.0"
ARGS+=" -machine virt"
ARGS+=" -object rng-random,filename=/dev/urandom,id=rng0"
ARGS+=" -device virtio-rng-device,rng=rng0"
if (( !$SERIAL )); then
CMDLINE+="kernel.serial=none "
fi
;;
x64)
QEMU=${QEMUDIR}qemu-system-x86_64
# The kernel only supports the v2.1 (32-bit) SMBIOS entry point.
ARGS+=" -machine q35,smbios-entry-point-type=32"
ARGS+=" -device isa-debug-exit,iobase=0xf4,iosize=0x04"
# Override the SeaBIOS serial port to keep it from outputting
# a terminal reset on start.
ARGS+=" -fw_cfg name=etc/sercon-port,string=0"
if (( $DO_KVM )); then
ARGS+=" -enable-kvm -cpu host,migratable=no,+invtsc"
else
ARGS+=" -cpu Skylake-Client,-check"
fi
if (( $SERIAL )); then
CMDLINE+="kernel.serial=legacy "
else
CMDLINE+="kernel.serial=none "
fi
;;
*)
echo unsupported arch
HELP
;;
esac
# Add entropy to the kernel
CMDLINE+="kernel.entropy-mixin=$(head -c 32 /dev/urandom | shasum -a 256 | awk '{ print $1 }') "
# Don't 'reboot' the emulator if the kernel crashes
CMDLINE+="kernel.halt-on-panic=true "
# Finally, append any values received via option. We save them for last so that
# they can override others since "last value wins".
CMDLINE+=$OPT_CMDLINE
cd /
# If dry run is set, simply echo all commands run from here on out.
if (( $DRY_RUN )); then
RUN_CMD="echo"
else
RUN_CMD="exec"
set -x
fi
# TODO(joshuaseaton): this and other command-line building logic above can be
# tidied up in having $ARGS be an array.
if (( $UEFI_DISK_BOOT )); then
$RUN_CMD $QEMU $ARGS "$@"
else
echo "CMDLINE: $CMDLINE"
$RUN_CMD $QEMU $ARGS -append "$CMDLINE" "$@"
fi