diff --git a/board/common/qemu/ddi/Config.in.in b/board/common/qemu/ddi/Config.in.in new file mode 100644 index 00000000..0f8097fe --- /dev/null +++ b/board/common/qemu/ddi/Config.in.in @@ -0,0 +1,279 @@ +mainmenu "QEMU @ARCH@ Virtualization" + +config QEMU_@ARCH@ + bool + default y + +config QEMU_ARCH + string + default "@ARCH@" + +config QEMU_VIRTIO_BUS + string + default "device" if QEMU_arm + default "pci" + +config QEMU_BIN_DDI + string + default "@IMAGE@" + +config QEMU_BIN_DISK + string + default "@DISK@" + +config QEMU_BIN_ESP + string + default "@ESP@" + +config QEMU_BIN_KERNEL + string + depends on QEMU_LOADER_QEMU + default "../zImage" if QEMU_arm + default "../Image" if QEMU_aarch64 + default "../bzImage" if QEMU_x86_64 + +config QEMU_BIN_LVM_STUB + string + default "../lvm-stub.disk" + + +choice + prompt "Loader" + default QEMU_LOADER_QEMU + +config QEMU_LOADER_QEMU + bool "QEMU" + +config QEMU_LOADER_OVMF + bool "OVMF (UEFI)" + +endchoice + +config QEMU_BIN_BIOS + string + depends on !QEMU_LOADER_KERNEL + default "OVMF.fd" if QEMU_LOADER_OVMF + +menu "Machine" + +config QEMU_MACHINE + string "Machine" + default "pc" if QEMU_x86_64 + default "virt" + +config QEMU_CPU + string "CPU" + default "host" if QEMU_x86_64 + default "cortex-a53" if QEMU_aarch64 + default "max" + +config QEMU_RAM + string "RAM size (k/M/G)" + default "384M" + +choice + prompt "Console" + default QEMU_CONSOLE_VIRTIO + +config QEMU_CONSOLE_VIRTIO + bool "Virtio (hvc0)" + +config QEMU_CONSOLE_SERIAL + bool "Serial (ttyS0/ttyAMA0)" + +endchoice + +config QEMU_CONSOLE + string + default "hvc0" if QEMU_CONSOLE_VIRTIO + default "ttyAMA0" if QEMU_arm + default "ttyAMA0" if QEMU_aarch64 + default "ttyS0" + +endmenu + +menu "Disks & Filesystems" + +config QEMU_DISK_INITRD + bool "Use the DDI as initrd" + depends on QEMU_LOADER_QEMU + help + Pass, and boot from, the DDI as the initial RAM disk. + + This is useful to test netbooting scenarios, e.g., in + combination with an empty system disk to simulate an initial + deployment. + +comment "System disk" + +choice + prompt "Interface" + default QEMU_DISK_SYS_IF_VIRTIO + +config QEMU_DISK_SYS_IF_VIRTIO + bool "virtio" + help + Attach system disks using virtio block devices. + +endchoice + +choice + prompt "Contents" + default QEMU_DISK_SYS_SPLIT + +config QEMU_DISK_SYS_EMPTY + bool "Empty disk" + help + Attach an empty disk. + + This is useful for testing things like installing Infix from a + USB mass storage device (see "USB Mass Storage" below). + +config QEMU_DISK_SYS_FULL + bool "Full system disk" + help + Use the full system disk containing: + - EFI partition (and backup) with Barebox + - LVM PV with Infix installed + + This is useful when testing system upgrades, fallback + mechanisms related to corrupt disks etc. + + WARNING: Usage of this option is fraught with peril as + incremental builds of Infix are likely to cause corruption in + the QCoW2 layer. If you do not understand the meaning of this + you most likely to NOT want to make use of this feature. + +config QEMU_DISK_SYS_NONE + bool "None" + help + Do not attach any system disk. + + This is useful for testing how Infix behaves when no persistent + storage is available - a common scenario during bringup of a + new board. + +config QEMU_DISK_SYS_SPLIT + bool "Split ESP/DDI/LVM" + help + Attach the EFI System partition, containing Barebox; the raw + DDI image; and an (initially empty) LVM stub for persistent + storage - each as a separate disk. + + This is the simplest way of testing out incremental builds of + Infix. Since the DDI is attached as a raw read-only disk, there + is no risk of CoW corruption like with a full system disk. + + NOTE: In this setup, the size below refers to the size of the + LVM stub. + +endchoice + +config QEMU_DISK_SYS_SIZE + string "Size (k/M/G)" + default "8G" + +comment "USB mass storage" + +choice + prompt "Contents" + default QEMU_DISK_USB_NONE + +config QEMU_DISK_USB_DISK + bool "Full system disk" + help + Attach the full system disk containing: + - EFI partition (and backup) with Barebox + - LVM PV with Infix installed + +config QEMU_DISK_USB_NONE + bool "None" + help + Do not attach any disk over USB. + +endchoice + +config QEMU_DISK_USB_SIZE + string "Size (k/M/G)" + depends on !QEMU_DISK_USB_NONE + default "4G" + +comment "Host filesystem passthrough (9P)" + +config QEMU_DISK_HOST + string "Path" + default "/tmp" + +endmenu + +menu "Networking" + +choice + prompt "Mode" + default QEMU_NET_USER + +config QEMU_NET_BRIDGE + bool "Bridged" + +config QEMU_NET_NONE + bool "None" + +config QEMU_NET_ROCKER + bool "Rocker" + +config QEMU_NET_TAP + bool "TAP" + +config QEMU_NET_USER + bool "User" + +endchoice + +config QEMU_NET_MODE + string + default "bridge" if QEMU_NET_BRIDGE + default "none" if QEMU_NET_NONE + default "rocker" if QEMU_NET_ROCKER + default "tap" if QEMU_NET_TAP + default "user" if QEMU_NET_USER + +config QEMU_NET_MODEL + string "Interface model" + default "virtio-net-device" if QEMU_arm + default "virtio-net-pci" + help + The default virtio NIC works for most use-cases, but if you + want to play with low-level stuff like ethtool, you might + want to test the Intel 82545EM driver, e1000. + + Note: ARM 32-bit uses virtio-net-device (MMIO) by default. + +config QEMU_NET_BRIDGE_DEV + string "Bridge device" + depends on QEMU_NET_BRIDGE + default "virbr0" + +config QEMU_NET_USER_OPTS + string "User mode options" + depends on QEMU_NET_USER + help + Extra -nic user, + +config QEMU_NET_TAP_N + int "Number of TAPs" + depends on QEMU_NET_TAP + default 1 + +config QEMU_NET_PORTS + int "Number of Rocker switch ports" + depends on QEMU_NET_ROCKER + default 10 + +endmenu + +config QEMU_EXTRA + string "Extra QEMU options" + +config QEMU_APPEND + string "Extra kernel options" + depends on QEMU_LOADER_QEMU diff --git a/board/common/qemu/ddi/run.sh b/board/common/qemu/ddi/run.sh new file mode 100755 index 00000000..9b085bef --- /dev/null +++ b/board/common/qemu/ddi/run.sh @@ -0,0 +1,480 @@ +#!/bin/sh +# This script can be used to start an Infix OS image in Qemu. It reads +# either a .config, generated from Config.in, or qemu.cfg from a release +# tarball, for the required configuration data. +# +# Debian/Ubuntu users can change the configuration post-release, install +# the kconfig-frontends package: +# +# sudo apt install kconfig-frontends +# +# and then call this script with: +# +# ./run.sh -c +# +# To bring up a menuconfig dialog. Select `Exit` and save the changes. +# For more help, see:_ +# +# ./run.sh -h +# +# shellcheck disable=SC3037 + +# Add /sbin to PATH for mkfs.ext4 and such (not default in debian) +export PATH="/sbin:/usr/sbin:$PATH" + +qdir=$(dirname "$(readlink -f "$0")") +imgdir=$(readlink -f "${qdir}/..") +prognm=$(basename "$0") + +usage() +{ + cat <&2 + exit 1 +} + +binpath() +{ + case "$1" in + ./*|../*) + # Relative paths are relative to the location of .config + printf "$qdir/$1" + ;; + *) + printf "$1" + ;; + esac +} + +q() +{ + local _cmd="$1" + shift + + case "$_cmd" in + sect) + printf ' \\\n\t' >>"$qdir"/qemu.sh + + case $# in + 1) + printf -- "-$1" >>"$qdir"/qemu.sh + _delim=" " + ;; + 2) + printf -- "-$1 $2" >>"$qdir"/qemu.sh + _delim="," + ;; + *) + die "q sect: Invalid arguments" + ;; + esac + ;; + param) + [ $# -eq 2 ] || die "q param: Takes exactly two arguments" + printf -- "$_delim$1=$2" >>"$qdir"/qemu.sh + _delim="," + ;; + option) + [ $# -eq 1 ] || die "q option: Takes exactly one argument" + printf -- "$_delim$1" >>"$qdir"/qemu.sh + _delim="," + ;; + *) + die "q: Unknown command: $_cmd" + ;; + esac +} + +q_sect_device_virtio() +{ + q sect device virtio-$1-$QEMU_VIRTIO_BUS +} + +q_add_disk() +{ + if [ "$QEMU_DISK_SYS_IF_VIRTIO" ]; then + q_sect_device_virtio blk + q param drive "$1" + else + die "Unknown system disk interface" + fi +} + +q_sect_device_usb() +{ + if [ -z "$usb_bus_added" ]; then + q sect usb + q sect device usb-ehci + q param id ehci + usb_bus_added=YES + fi + + q sect device "$1" + q param bus ehci.0 +} + +qcowed() +{ + local _qcow="$qdir"/"$1".qcow2 + local _base _size + + if [ -f "$_qcow" ] && qemu-img check -q "$_qcow"; then + echo "$_qcow" + return + fi + + rm -f "$_qcow" + + case $# in + 2) + _size="$2" + + qemu-img create -q -f qcow2 "$_qcow" "$_size" \ + || die "Unable to create $1" + ;; + 3) + + _base="$2" + _size="$3" + + [ "$_size" = "auto" ] && _size= + + qemu-img create -q -f qcow2 -F raw \ + -o backing_file="$_base" "$_qcow" $_size \ + || die "Unable to create CoW layer for $_base" + ;; + *) + die "qcowed: usage: qcowed [] " + ;; + esac + + echo "$_qcow" +} + +append() +{ + echo -n " $*" >>"$qdir"/append +} + +gen_machine() +{ + q sect machine + q param type "$QEMU_MACHINE" + q param accel kvm:tcg + + q sect cpu "$QEMU_CPU" + + q sect m + q param size "$QEMU_RAM" +} + +gen_loader() +{ + if [ "$QEMU_LOADER_QEMU" ]; then + q sect kernel $(binpath "$QEMU_BIN_KERNEL") + elif [ "$QEMU_LOADER_OVMF" ]; then + q sect bios $(binpath "$QEMU_BIN_BIOS") + fi +} + +gen_serial() +{ + q sect display none + + q_sect_device_virtio serial + + q sect chardev stdio + q param id console0 + q param mux on + + q sect mon + q param chardev console0 + + case "$QEMU_CONSOLE" in + hvc0) + q sect device virtconsole + q param nr 0 + q param name console + q param chardev console0 + ;; + ttyS0|ttyAMA0) + q sect serial chardev:console0 + ;; + *) + die "Unsupported console: $QEMU_CONSOLE" + ;; + esac + + append console="$QEMU_CONSOLE" + + [ "$V" ] && append debug || append loglevel=4 +} + +gpt_uuid() +{ + sgdisk "$1" -p | awk ' + BEGIN { err = 1; } END { exit(err); } + /^Creating new GPT/ { exit; } + + /^Disk identifier \(GUID\): / { + print($4); err = 0; exit; + }' || die "Unable to determine GPT UUID of $1" +} + +gen_disk_sys_empty() +{ + q sect drive + q param id disk-sys + q param format qcow2 + q param if none + q param file $(qcowed disk-sys "$QEMU_DISK_SYS_SIZE") + + q_add_disk disk-sys +} + +gen_disk_sys_full() +{ + q sect drive + q param id disk-sys + q param format qcow2 + q param if none + q param file $(qcowed disk-sys $(binpath "$QEMU_BIN_DISK") \ + "$QEMU_DISK_SYS_SIZE") + + q_add_disk disk-sys + + append root=ddi:lvm:internal/$(basename "$QEMU_BIN_DDI") +} + +gen_disk_sys_split() +{ + # EFI System Partition + q sect drive + q param id disk-esp + q param format qcow2 + q param if none + q param file $(qcowed disk-esp $(binpath "$QEMU_BIN_ESP") auto) + + q_add_disk disk-esp + + # LVM stub + q sect drive + q param id disk-sys + q param format qcow2 + q param if none + q param file $(qcowed disk-sys $(binpath "$QEMU_BIN_LVM_STUB") \ + "$QEMU_DISK_SYS_SIZE") + + q_add_disk disk-sys + + # DDI (ro) + q sect drive + q param id disk-ddi + q param format raw + q param file $(binpath "$QEMU_BIN_DDI") + q param if none + q param read-only on + + q_add_disk disk-ddi + + append root=ddi:GPTUUID=$(gpt_uuid $(binpath "$QEMU_BIN_DDI")) +} + +gen_disk_sys() +{ + [ "$QEMU_DISK_SYS_NONE" ] && return + + if [ "$QEMU_DISK_SYS_EMPTY" ]; then + gen_disk_sys_empty + elif [ "$QEMU_DISK_SYS_FULL" ]; then + gen_disk_sys_full + elif [ "$QEMU_DISK_SYS_SPLIT" ]; then + gen_disk_sys_split + else + die "Unknown system disk mode" + fi +} + +gen_disk_usb() +{ + [ "$QEMU_DISK_USB_NONE" ] && return + + if [ "$QEMU_DISK_USB_DISK" ]; then + q sect drive + q param id disk-usb + q param format qcow2 + q param file $(qcowed disk-usb \ + $(binpath "$QEMU_BIN_DISK") \ + "$QEMU_DISK_USB_SIZE") + q param if none + + q_sect_device_usb usb-storage + q param drive disk-usb + else + die "Unknown USB attachment" + fi +} + +gen_disk_initrd() +{ + [ "$QEMU_DISK_INITRD" ] || return + + q sect initrd $(binpath "$QEMU_BIN_DDI") + append root=ddi:/initrd.image +} + +internal_is_available() +{ + [ "$QEMU_DISK_SYS_FULL" ] || [ "$QEMU_DISK_SYS_SPLIT" ] \ + || [ "$QEMU_DISK_USB_DISK" ] +} + +gen_disks() +{ + gen_disk_sys + gen_disk_usb + + gen_disk_initrd + + internal_is_available || append rd.nointernal +} + +gen_gdb() +{ + # Create a UNIX socket on the host that is connected to a virtio + # console in the guest, which gdbserver can attach to for + # userspace debugging. + q sect chardev socket + q param id gdbserver + q param path "$qdir"/gdbserver.sock + q param server on + q param wait off + + q sect device virtconsole + q param nr 1 + q param name gdbserver + q param chardev gdbserver + + # Create a UNIX socket on the host that is connected to QEMU's GDB + # stub, for bootloader/kernel debugging. + q sect chardev socket + q param id gdbqemu + q param path "$qdir"/gdbqemu.sock + q param server on + q param wait off + + q sect gdb chardev:gdbqemu +} + +gen_all() +{ + local _append + + : >"$qdir"/append + cat <"$qdir"/qemu.sh +#!/bin/sh + +echo "Starting Qemu :: Ctrl-a x -- exit | Ctrl-a c -- toggle console/monitor" + +line=\$(stty -g) +stty raw +trap 'stty "\$line"' EXIT INT TERM + +qemu-system-$QEMU_ARCH -nodefaults \\ +EOF + chmod +x "$qdir"/qemu.sh + + gen_machine + gen_loader + gen_serial + gen_disks + gen_gdb + + if [ "$QEMU_LOADER_QEMU" ]; then + _append=$(cat "$qdir"/append) + q sect append "\"$_append $QEMU_APPEND $*\"" + fi + + echo >>"$qdir"/qemu.sh +} + +menuconfig() +{ + command -v kconfig-mconf >/dev/null \ + || die "cannot find kconfig-mconf for menuconfig" + + CONFIG_= KCONFIG_CONFIG="$qdir"/.config \ + kconfig-mconf "$qdir"/Config.in +} + +_generate=YES + +while getopts "0cGh-" opt; do + case ${opt} in + 0) + echo "Clearing all copy-on-write layers" >&2 + rm -f "$qdir"/*.qcow2 + exit 0 + ;; + c) + menuconfig + ;; + G) + _generate= + ;; + h) + usage && exit 0 + ;; + -) + break + ;; + *) + usage && exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +# shellcheck disable=SC1090 +. "$qdir"/.config + +[ "$_generate" ] && gen_all "$*" + +exec "$qdir"/qemu.sh diff --git a/board/common/qemu/Config.in.in b/board/common/qemu/itb/Config.in.in similarity index 100% rename from board/common/qemu/Config.in.in rename to board/common/qemu/itb/Config.in.in diff --git a/board/common/qemu/run.sh b/board/common/qemu/itb/run.sh similarity index 100% rename from board/common/qemu/run.sh rename to board/common/qemu/itb/run.sh diff --git a/board/common/qemu/qemu.mk b/board/common/qemu/qemu.mk index 336507aa..2c7be5be 100644 --- a/board/common/qemu/qemu.mk +++ b/board/common/qemu/qemu.mk @@ -4,9 +4,15 @@ # ################################################################################ -QEMU_SCRIPTS_DIR := $(pkgdir) +qemu-scripts-dir := $(pkgdir)/$(if $(IMAGE_DDI),ddi,itb) +qemu-image := ../$(INFIX_ARTIFACT).$(if $(IMAGE_DDI),raw,qcow2) +qemu-disk := $(if $(IMAGE_DDI_DISK),../$(INFIX_ARTIFACT).disk) +qemu-esp := $(if $(IMAGE_BAREBOX_ESP),../barebox-esp.vfat) + +qemu-kconfig-prefix := $(if $(IMAGE_DDI),,CONFIG_) + qemu-kconfig = \ - CONFIG_="CONFIG_" \ + CONFIG_="$(qemu-kconfig-prefix)" \ BR2_CONFIG="$(BINARIES_DIR)/qemu/.config" \ $(BUILD_DIR)/buildroot-config/$(1) $(2) "$(BINARIES_DIR)/qemu/Config.in" @@ -25,17 +31,19 @@ qemu-scripts: \ $(BINARIES_DIR)/qemu/Config.in \ $(BINARIES_DIR)/qemu/.config -$(BINARIES_DIR)/qemu/run.sh: $(QEMU_SCRIPTS_DIR)/run.sh +$(BINARIES_DIR)/qemu/run.sh: $(qemu-scripts-dir)/run.sh @$(call IXMSG,"Installing QEMU scripts") @mkdir -p $(dir $@) @cp $< $@ -$(BINARIES_DIR)/qemu/Config.in: $(QEMU_SCRIPTS_DIR)/Config.in.in +$(BINARIES_DIR)/qemu/Config.in: $(qemu-scripts-dir)/Config.in.in @mkdir -p $(dir $@) @sed \ - -e "s:@ARCH@:QEMU_$(BR2_ARCH):" \ - -e "s:@DISK_IMG@:../$(INFIX_ARTIFACT).qcow2:" \ - < $< >$@ + -e "s:@ARCH@:$(BR2_ARCH):g" \ + -e "s:@IMAGE@:$(qemu-image):g" \ + -e "s:@DISK@:$(qemu-disk):g" \ + -e "s:@ESP@:$(qemu-esp):g" \ + <$< >$@ $(BINARIES_DIR)/qemu/.config: $(BINARIES_DIR)/qemu/Config.in @$(call qemu-kconfig,conf,--olddefconfig)