--- /dev/null
+#
+# Copyright 2019-2021 Canonical Ltd.
+# Authors:
+# - dann frazier <dann.frazier@canonical.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import enum
+import os
+import shutil
+import tempfile
+
+
+class QemuEfiMachine(enum.Enum):
+ OVMF_PC = enum.auto()
+ OVMF_Q35 = enum.auto()
+ OVMF32 = enum.auto()
+ AAVMF = enum.auto()
+ AAVMF32 = enum.auto()
+
+
+class QemuEfiVariant(enum.Enum):
+ MS = enum.auto()
+ SECBOOT = enum.auto()
+ SNAKEOIL = enum.auto()
+
+
+class QemuEfiFlashSize(enum.Enum):
+ DEFAULT = enum.auto
+ SIZE_2MB = enum.auto()
+ SIZE_4MB = enum.auto()
+
+
+class QemuCommand:
+ # Based on the args used by ovmf-vars-generator
+ Qemu_Common_Params = [
+ '-no-user-config', '-nodefaults',
+ '-m', '256',
+ '-smp', '2,sockets=2,cores=1,threads=1',
+ '-display', 'none',
+ '-serial', 'stdio',
+ ]
+ Ovmf_Common_Params = Qemu_Common_Params + [
+ '-chardev', 'pty,id=charserial1',
+ '-device', 'isa-serial,chardev=charserial1,id=serial1',
+ ]
+ Aavmf_Common_Params = Qemu_Common_Params + [
+ '-machine', 'virt', '-device', 'virtio-serial-device',
+ ]
+ Machine_Base_Command = {
+ QemuEfiMachine.AAVMF: [
+ 'qemu-system-aarch64', '-cpu', 'cortex-a57',
+ ] + Aavmf_Common_Params,
+ QemuEfiMachine.AAVMF32: [
+ 'qemu-system-aarch64', '-cpu', 'cortex-a15',
+ ] + Aavmf_Common_Params,
+ QemuEfiMachine.OVMF_PC: [
+ 'qemu-system-x86_64', '-machine', 'pc,accel=tcg',
+ ] + Ovmf_Common_Params,
+ QemuEfiMachine.OVMF_Q35: [
+ 'qemu-system-x86_64', '-machine', 'q35,accel=tcg',
+ ] + Ovmf_Common_Params,
+ QemuEfiMachine.OVMF32: [
+ 'qemu-system-i386', '-machine', 'q35,accel=tcg',
+ ] + Ovmf_Common_Params,
+ }
+
+ def _get_default_flash_paths(self, machine, variant, flash_size):
+ assert(machine in QemuEfiMachine)
+ assert(variant is None or variant in QemuEfiVariant)
+ assert(flash_size in QemuEfiFlashSize)
+
+ code_ext = vars_ext = ''
+ if variant == QemuEfiVariant.MS:
+ code_ext = vars_ext = '.ms'
+ elif variant == QemuEfiVariant.SECBOOT:
+ code_ext = '.secboot'
+ elif variant == QemuEfiVariant.SNAKEOIL:
+ vars_ext = '.snakeoil'
+
+ if machine == QemuEfiMachine.AAVMF:
+ assert(flash_size == QemuEfiFlashSize.DEFAULT)
+ return (
+ f'/usr/share/AAVMF/AAVMF_CODE{code_ext}.fd',
+ f'/usr/share/AAVMF/AAVMF_VARS{code_ext}.fd',
+ )
+ if machine == QemuEfiMachine.AAVMF32:
+ assert(variant is None)
+ assert(flash_size == QemuEfiFlashSize.DEFAULT)
+ return (
+ '/usr/share/AAVMF/AAVMF32_CODE.fd',
+ '/usr/share/AAVMF/AAVMF32_VARS.fd'
+ )
+ if machine == QemuEfiMachine.OVMF32:
+ assert(variant is None or variant in [QemuEfiVariant.SECBOOT])
+ assert(
+ flash_size in [
+ QemuEfiFlashSize.DEFAULT, QemuEfiFlashSize.SIZE_4MB
+ ]
+ )
+ return (
+ '/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd',
+ '/usr/share/OVMF/OVMF32_VARS_4M.fd',
+ )
+ # Remaining possibilities are OVMF variants
+ if machine == QemuEfiMachine.OVMF_PC:
+ assert(variant is None)
+ if variant == QemuEfiVariant.SNAKEOIL:
+ # We provide one size - you don't get to pick.
+ assert(flash_size == QemuEfiFlashSize.DEFAULT)
+ size_ext = '' if flash_size == QemuEfiFlashSize.SIZE_2MB else '_4M'
+ return (
+ f'/usr/share/OVMF/OVMF_CODE{size_ext}{code_ext}.fd',
+ f'/usr/share/OVMF/OVMF_VARS{size_ext}{vars_ext}.fd'
+ )
+
+ def __init__(
+ self, machine, variant=None,
+ code_path=None, vars_template_path=None,
+ flash_size=QemuEfiFlashSize.DEFAULT,
+ ):
+ assert(
+ (code_path and vars_template_path) or
+ (not code_path and not vars_template_path)
+ )
+
+ if not code_path:
+ (code_path, vars_template_path) = self._get_default_flash_paths(
+ machine, variant, flash_size)
+
+ self.pflash = self.PflashParams(code_path, vars_template_path)
+ self.command = self.Machine_Base_Command[machine] + self.pflash.params
+ if variant in [QemuEfiVariant.MS, QemuEfiVariant.SECBOOT] and \
+ flash_size == QemuEfiFlashSize.SIZE_2MB:
+ # 2MB images have 64-bit PEI that does not support S3 w/ SMM
+ self.command.extend(['-global', 'ICH9-LPC.disable_s3=1'])
+
+ def add_disk(self, path):
+ self.command = self.command + [
+ '-drive', 'file=%s,format=raw' % (path)
+ ]
+
+ def add_oem_string(self, type, string):
+ string = string.replace(",", ",,")
+ self.command = self.command + [
+ '-smbios', f'type={type},value={string}'
+ ]
+
+ class PflashParams:
+ '''
+ Used to generate the appropriate -pflash arguments for QEMU. Mostly
+ used as a fancy way to generate a per-instance vars file and have it
+ be automatically cleaned up when the object is destroyed.
+ '''
+ def __init__(self, code_path, vars_template_path):
+ with tempfile.NamedTemporaryFile(delete=False) as varfile:
+ self.varfile_path = varfile.name
+ with open(vars_template_path, 'rb') as template:
+ shutil.copyfileobj(template, varfile)
+ self.params = [
+ '-drive',
+ 'file=%s,if=pflash,format=raw,unit=0,readonly=on' %
+ (code_path),
+ '-drive',
+ 'file=%s,if=pflash,format=raw,unit=1,readonly=off' %
+ (varfile.name)
+ ]
+
+ def __del__(self):
+ os.unlink(self.varfile_path)