xref: /freebsd/share/examples/bhyve/vmrun.sh (revision 59c8e88e72633afbc47a4ace0d2170d00d51f7dc)
1#!/bin/sh
2#
3# SPDX-License-Identifier: BSD-2-Clause
4#
5# Copyright (c) 2013 NetApp, Inc.
6# All rights reserved.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions
10# are met:
11# 1. Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in the
15#    documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27# SUCH DAMAGE.
28#
29#
30
31LOADER=/usr/sbin/bhyveload
32BHYVECTL=/usr/sbin/bhyvectl
33FBSDRUN=/usr/sbin/bhyve
34
35DEFAULT_MEMSIZE=512M
36DEFAULT_CPUS=2
37DEFAULT_TAPDEV=tap0
38DEFAULT_CONSOLE=stdio
39
40DEFAULT_NIC=virtio-net
41DEFAULT_DISK=virtio-blk
42DEFAULT_VIRTIO_DISK="./diskdev"
43DEFAULT_ISOFILE="./release.iso"
44
45DEFAULT_VNCHOST="127.0.0.1"
46DEFAULT_VNCPORT=5900
47DEFAULT_VNCSIZE="w=1024,h=768"
48
49errmsg() {
50	echo "*** $1"
51}
52
53usage() {
54	local msg=$1
55
56	echo "Usage: vmrun.sh [-aAEhiTuvw] [-c <CPUs>] [-C <console>]" \
57	    "[-d <disk file>]"
58	echo "                [-e <name=value>] [-f <path of firmware>]" \
59	    "[-F <size>]"
60	echo "                [-G [w][address:]port] [-H <directory>]"
61	echo "                [-I <location of installation iso>] [-l <loader>]"
62	echo "                [-L <VNC IP for UEFI framebuffer>]"
63	echo "                [-m <memsize>]" \
64	    "[-n <network adapter emulation type>]"
65	echo "                [-p <pcidev|bus/slot/func>]"
66	echo "                [-P <port>] [-t <tapdev>] <vmname>"
67	echo ""
68	echo "       -h: display this help message"
69	echo "       -a: force memory mapped local APIC access"
70	echo "       -A: use AHCI disk emulation instead of ${DEFAULT_DISK}"
71	echo "       -c: number of virtual cpus (default: ${DEFAULT_CPUS})"
72	echo "       -C: console device (default: ${DEFAULT_CONSOLE})"
73	echo "       -d: virtio diskdev file (default: ${DEFAULT_VIRTIO_DISK})"
74	echo "       -e: set FreeBSD loader environment variable"
75	echo "       -E: Use UEFI mode"
76	echo "       -f: Use a specific UEFI firmware"
77	echo "       -F: Use a custom UEFI GOP framebuffer size" \
78	    "(default: ${DEFAULT_VNCSIZE})"
79	echo "       -G: bind the GDB stub to the specified address"
80	echo "       -H: host filesystem to export to the loader"
81	echo "       -i: force boot of the Installation CDROM image"
82	echo "       -I: Installation CDROM image location" \
83	    "(default: ${DEFAULT_ISOFILE})"
84	echo "       -l: the OS loader to use (default: /boot/userboot.so)"
85	echo "       -L: IP address for UEFI GOP VNC server" \
86	    "(default: ${DEFAULT_VNCHOST})"
87	echo "       -m: memory size (default: ${DEFAULT_MEMSIZE})"
88	echo "       -n: network adapter emulation type" \
89	    "(default: ${DEFAULT_NIC})"
90	echo "       -p: pass-through a host PCI device (e.g ppt0 or" \
91	    "bus/slot/func)"
92	echo "       -P: UEFI GOP VNC port (default: ${DEFAULT_VNCPORT})"
93	echo "       -t: tap device for virtio-net (default: $DEFAULT_TAPDEV)"
94	echo "       -T: Enable tablet device (for UEFI GOP)"
95	echo "       -u: RTC keeps UTC time"
96	echo "       -v: Wait for VNC client connection before booting VM"
97	echo "       -w: ignore unimplemented MSRs"
98	echo ""
99	[ -n "$msg" ] && errmsg "$msg"
100	exit 1
101}
102
103if [ `id -u` -ne 0 ]; then
104	errmsg "This script must be executed with superuser privileges"
105	exit 1
106fi
107
108kldstat -n vmm > /dev/null 2>&1
109if [ $? -ne 0 ]; then
110	errmsg "vmm.ko is not loaded"
111	exit 1
112fi
113
114force_install=0
115isofile=${DEFAULT_ISOFILE}
116memsize=${DEFAULT_MEMSIZE}
117console=${DEFAULT_CONSOLE}
118cpus=${DEFAULT_CPUS}
119nic=${DEFAULT_NIC}
120tap_total=0
121disk_total=0
122disk_emulation=${DEFAULT_DISK}
123loader_opt=""
124bhyverun_opt="-H -A -P"
125pass_total=0
126
127# EFI-specific options
128efi_mode=0
129efi_firmware="/usr/local/share/uefi-firmware/BHYVE_UEFI.fd"
130vncwait=""
131vnchost=${DEFAULT_VNCHOST}
132vncport=${DEFAULT_VNCPORT}
133vncsize=${DEFAULT_VNCSIZE}
134tablet=""
135
136while getopts aAc:C:d:e:Ef:F:G:hH:iI:l:L:m:n:p:P:t:Tuvw c ; do
137	case $c in
138	a)
139		bhyverun_opt="${bhyverun_opt} -a"
140		;;
141	A)
142		disk_emulation="ahci-hd"
143		;;
144	c)
145		cpus=${OPTARG}
146		;;
147	C)
148		console=${OPTARG}
149		;;
150	d)
151		disk_dev=${OPTARG%%,*}
152		disk_opts=${OPTARG#${disk_dev}}
153		eval "disk_dev${disk_total}=\"${disk_dev}\""
154		eval "disk_opts${disk_total}=\"${disk_opts}\""
155		disk_total=$(($disk_total + 1))
156		;;
157	e)
158		loader_opt="${loader_opt} -e ${OPTARG}"
159		;;
160	E)
161		efi_mode=1
162		;;
163	f)
164		efi_firmware="${OPTARG}"
165		;;
166	F)
167		vncsize="${OPTARG}"
168		;;
169	G)
170		bhyverun_opt="${bhyverun_opt} -G ${OPTARG}"
171		;;
172	H)
173		host_base=`realpath ${OPTARG}`
174		;;
175	i)
176		force_install=1
177		;;
178	I)
179		isofile=${OPTARG}
180		;;
181	l)
182		loader_opt="${loader_opt} -l ${OPTARG}"
183		;;
184	L)
185		vnchost="${OPTARG}"
186		;;
187	m)
188		memsize=${OPTARG}
189		;;
190	n)
191		nic=${OPTARG}
192		;;
193	p)
194		eval "pass_dev${pass_total}=\"${OPTARG}\""
195		pass_total=$(($pass_total + 1))
196		;;
197	P)
198		vncport="${OPTARG}"
199		;;
200	t)
201		eval "tap_dev${tap_total}=\"${OPTARG}\""
202		tap_total=$(($tap_total + 1))
203		;;
204	T)
205		tablet="-s 30,xhci,tablet"
206		;;
207	u)
208		bhyverun_opt="${bhyverun_opt} -u"
209		;;
210	v)
211		vncwait=",wait"
212		;;
213	w)
214		bhyverun_opt="${bhyverun_opt} -w"
215		;;
216	*)
217		usage
218		;;
219	esac
220done
221
222if [ $tap_total -eq 0 ] ; then
223    tap_total=1
224    tap_dev0="${DEFAULT_TAPDEV}"
225fi
226if [ $disk_total -eq 0 ] ; then
227    disk_total=1
228    disk_dev0="${DEFAULT_VIRTIO_DISK}"
229
230fi
231
232shift $((${OPTIND} - 1))
233
234if [ $# -ne 1 ]; then
235	usage "virtual machine name not specified"
236fi
237
238vmname="$1"
239if [ -n "${host_base}" ]; then
240	loader_opt="${loader_opt} -h ${host_base}"
241fi
242
243# If PCI passthru devices are configured then guest memory must be wired
244if [ ${pass_total} -gt 0 ]; then
245	loader_opt="${loader_opt} -S"
246	bhyverun_opt="${bhyverun_opt} -S"
247fi
248
249if [ ${efi_mode} -gt 0 ]; then
250	if [ ! -f ${efi_firmware} ]; then
251		echo "Error: EFI Firmware ${efi_firmware} doesn't exist." \
252		    "Try: pkg install edk2-bhyve"
253		exit 1
254	fi
255fi
256
257make_and_check_diskdev()
258{
259    local virtio_diskdev="$1"
260    # Create the virtio diskdev file if needed
261    if [ ! -e ${virtio_diskdev} ]; then
262	    echo "virtio disk device file \"${virtio_diskdev}\" does not exist."
263	    echo "Creating it ..."
264	    truncate -s 8G ${virtio_diskdev} > /dev/null
265    fi
266
267    if [ ! -r ${virtio_diskdev} ]; then
268	    echo "virtio disk device file \"${virtio_diskdev}\" is not readable"
269	    exit 1
270    fi
271
272    if [ ! -w ${virtio_diskdev} ]; then
273	    echo "virtio disk device file \"${virtio_diskdev}\" is not writable"
274	    exit 1
275    fi
276}
277
278echo "Launching virtual machine \"$vmname\" ..."
279
280first_diskdev="$disk_dev0"
281
282${BHYVECTL} --vm=${vmname} --destroy > /dev/null 2>&1
283
284while [ 1 ]; do
285
286	file -s ${first_diskdev} | grep "boot sector" > /dev/null
287	rc=$?
288	if [ $rc -ne 0 ]; then
289		file -s ${first_diskdev} | \
290		    grep ": Unix Fast File sys" > /dev/null
291		rc=$?
292	fi
293	if [ $rc -ne 0 ]; then
294		need_install=1
295	else
296		need_install=0
297	fi
298
299	if [ $force_install -eq 1 -o $need_install -eq 1 ]; then
300		if [ ! -r ${isofile} ]; then
301			echo -n "Installation CDROM image \"${isofile}\" "
302			echo    "is not readable"
303			exit 1
304		fi
305		BOOTDISKS="-d ${isofile}"
306		installer_opt="-s 31:0,ahci-cd,${isofile}"
307	else
308		BOOTDISKS=""
309		i=0
310		while [ $i -lt $disk_total ] ; do
311			eval "disk=\$disk_dev${i}"
312			if [ -r ${disk} ] ; then
313				BOOTDISKS="$BOOTDISKS -d ${disk} "
314			fi
315			i=$(($i + 1))
316		done
317		installer_opt=""
318	fi
319
320	if [ ${efi_mode} -eq 0 ]; then
321		${LOADER} -c ${console} -m ${memsize} ${BOOTDISKS} \
322		    ${loader_opt} ${vmname}
323		bhyve_exit=$?
324		if [ $bhyve_exit -ne 0 ]; then
325			break
326		fi
327	fi
328
329	#
330	# Build up args for additional tap and disk devices now.
331	#
332	nextslot=2  # slot 0 is hostbridge, slot 1 is lpc
333	devargs=""  # accumulate disk/tap args here
334	i=0
335	while [ $i -lt $tap_total ] ; do
336	    eval "tapname=\$tap_dev${i}"
337	    devargs="$devargs -s $nextslot:0,${nic},${tapname} "
338	    nextslot=$(($nextslot + 1))
339	    i=$(($i + 1))
340	done
341
342	i=0
343	while [ $i -lt $disk_total ] ; do
344	    eval "disk=\$disk_dev${i}"
345	    eval "opts=\$disk_opts${i}"
346	    make_and_check_diskdev "${disk}"
347	    devargs="$devargs -s $nextslot:0,$disk_emulation,${disk}${opts} "
348	    nextslot=$(($nextslot + 1))
349	    i=$(($i + 1))
350	done
351
352	i=0
353	while [ $i -lt $pass_total ] ; do
354		eval "pass=\$pass_dev${i}"
355		bsfform="$(echo "${pass}" | grep "^[0-9]\+/[0-9]\+/[0-9]\+$")"
356		if [ -z "${bsfform}" ]; then
357			bsf="$(pciconf -l "${pass}" 2>/dev/null)"
358			if [ $? -ne 0 ]; then
359				errmsg "${pass} is not a host PCI device"
360				exit 1
361			fi
362			bsf="$(echo "${bsf}" | awk -F: '{print $2"/"$3"/"$4}')"
363		else
364			bsf="${pass}"
365		fi
366		devargs="$devargs -s $nextslot:0,passthru,${bsf} "
367		nextslot=$(($nextslot + 1))
368		i=$(($i + 1))
369        done
370
371	efiargs=""
372	if [ ${efi_mode} -gt 0 ]; then
373		efiargs="-s 29,fbuf,tcp=${vnchost}:${vncport},"
374		efiargs="${efiargs}${vncsize}${vncwait}"
375		efiargs="${efiargs} -l bootrom,${efi_firmware}"
376		efiargs="${efiargs} ${tablet}"
377	fi
378
379	${FBSDRUN} -c ${cpus} -m ${memsize} ${bhyverun_opt}		\
380		-s 0:0,hostbridge					\
381		-s 1:0,lpc						\
382		${efiargs}						\
383		${devargs}						\
384		-l com1,${console}					\
385		${installer_opt}					\
386		${vmname}
387
388	bhyve_exit=$?
389	# bhyve returns the following status codes:
390	#  0 - VM has been reset
391	#  1 - VM has been powered off
392	#  2 - VM has been halted
393	#  3 - VM generated a triple fault
394	#  all other non-zero status codes are errors
395	#
396	if [ $bhyve_exit -ne 0 ]; then
397		break
398	fi
399done
400
401
402case $bhyve_exit in
403	0|1|2)
404		# Cleanup /dev/vmm entry when bhyve did not exit
405		# due to an error.
406		${BHYVECTL} --vm=${vmname} --destroy > /dev/null 2>&1
407		;;
408esac
409
410exit $bhyve_exit
411