Orzelius 3a1163692d
chore: cross platform qemu preflight checks
* split platform specific preflight checks into platform specific files
* error on darwin if target is amd64
* error on darwin if host platform is amd64
* add darwin specific var-file and pflash file locations
* remove hard kvm requirement (so linux logic can be tested on machines without kvm)

Signed-off-by: Orzelius <33936483+Orzelius@users.noreply.github.com>
Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2025-05-01 14:44:29 +04:00

267 lines
6.1 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package qemu
import (
"errors"
"fmt"
"os/exec"
"path/filepath"
"runtime"
"slices"
)
// Arch abstracts away differences between different architectures.
type Arch string
// Arch constants.
const (
ArchAmd64 Arch = "amd64"
ArchArm64 Arch = "arm64"
)
// Valid returns an error if the architecture is not supported.
func (arch Arch) Valid() error {
switch arch {
case ArchArm64:
return nil
case ArchAmd64:
if runtime.GOOS == "darwin" {
return errors.New("only arm emulation is supported on darwin")
}
return nil
default:
return fmt.Errorf("unsupported arch: %q", arch)
}
}
// QemuArch defines which qemu binary to use.
func (arch Arch) QemuArch() string {
switch arch {
case ArchAmd64:
return "x86_64"
case ArchArm64:
return "aarch64"
default:
panic("unsupported architecture")
}
}
// QemuMachine defines the machine type for qemu.
func (arch Arch) QemuMachine() string {
switch arch {
case ArchAmd64:
return "q35"
case ArchArm64:
return "virt,gic-version=max"
default:
panic("unsupported architecture")
}
}
// Console defines proper argument for the kernel to send logs to serial console.
func (arch Arch) Console() string {
switch arch {
case ArchAmd64:
return "ttyS0"
case ArchArm64:
return "ttyAMA0,115200n8"
default:
panic("unsupported architecture")
}
}
// PFlash for UEFI boot.
type PFlash struct {
Size int64
SourcePaths []string
}
// PFlash returns settings for parallel flash.
func (arch Arch) PFlash(uefiEnabled bool, extraUEFISearchPaths []string) []PFlash {
switch arch {
case ArchArm64:
// default search paths
uefiSourcePathPrefixes := []string{
"/usr/share/AAVMF", // most standard location
"/usr/share/qemu-efi-aarch64",
"/usr/share/OVMF",
"/usr/share/edk2/aarch64", // Fedora
"/usr/share/edk2/experimental", // Fedora
"/opt/homebrew/share/qemu", // Darwin
}
// Secure boot enabled firmware files
uefiSourceFiles := []string{
"AAVMF_CODE.secboot.fd", // debian, EFI vars not protected
"QEMU_EFI.secboot.testonly.fd", // Fedora, ref: https://bugzilla.redhat.com/show_bug.cgi?id=1882135
}
// Non-secure boot firmware files
uefiSourceFilesInsecure := []string{
"AAVMF_CODE.fd",
"QEMU_EFI.fd",
"OVMF.stateless.fd",
"edk2-aarch64-code.fd",
}
// Empty vars files
uefiVarsFiles := []string{
"AAVMF_VARS.fd",
"QEMU_VARS.fd",
"edk2-arm-vars.fd",
}
// Append extra search paths
uefiSourcePathPrefixes = append(uefiSourcePathPrefixes, extraUEFISearchPaths...)
uefiSourcePaths, uefiVarsPaths := generateUEFIPFlashList(uefiSourcePathPrefixes, uefiSourceFiles, uefiVarsFiles, uefiSourceFilesInsecure)
return []PFlash{
{
Size: 64 * 1024 * 1024,
SourcePaths: uefiSourcePaths,
},
{
SourcePaths: uefiVarsPaths,
Size: 64 * 1024 * 1024,
},
}
case ArchAmd64:
if !uefiEnabled {
return nil
}
// Default search paths
uefiSourcePathPrefixes := []string{
"/usr/share/ovmf",
"/usr/share/OVMF",
"/usr/share/qemu",
"/usr/share/ovmf/x64", // Arch Linux
}
// Secure boot enabled firmware files
uefiSourceFiles := []string{
"OVMF_CODE_4M.secboot.fd",
"OVMF_CODE.secboot.4m.fd", // Arch Linux
"OVMF_CODE.secboot.fd",
"OVMF.secboot.fd",
"edk2-x86_64-secure-code.fd", // Alpine Linux
"ovmf-x86_64-ms-4m-code.bin",
}
// Non-secure boot firmware files
uefiSourceFilesInsecure := []string{
"OVMF_CODE_4M.fd",
"OVMF_CODE.4m.fd", // Arch Linux
"OVMF_CODE.fd",
"OVMF.fd",
"ovmf-x86_64-4m-code.bin",
}
// Empty vars files
uefiVarsFiles := []string{
"OVMF_VARS_4M.fd",
"OVMF_VARS.4m.fd", // Arch Linux
"OVMF_VARS.fd",
"ovmf-x86_64-4m-vars.bin",
}
// Append extra search paths
uefiSourcePathPrefixes = append(uefiSourcePathPrefixes, extraUEFISearchPaths...)
uefiSourcePaths, uefiVarsPaths := generateUEFIPFlashList(uefiSourcePathPrefixes, uefiSourceFiles, uefiVarsFiles, uefiSourceFilesInsecure)
return []PFlash{
{
Size: 0,
SourcePaths: uefiSourcePaths,
},
{
Size: 0,
SourcePaths: uefiVarsPaths,
},
}
default:
return nil
}
}
func generateUEFIPFlashList(uefiSourcePathPrefixes, uefiSourceFiles, uefiVarsFiles, uefiSourceFilesInsecure []string) (uefiSourcePaths, uefiVarsPaths []string) {
for _, p := range uefiSourcePathPrefixes {
for _, f := range uefiSourceFiles {
uefiSourcePaths = append(uefiSourcePaths, filepath.Join(p, f))
}
for _, f := range uefiVarsFiles {
uefiVarsPaths = append(uefiVarsPaths, filepath.Join(p, f))
}
}
for _, p := range uefiSourcePathPrefixes {
for _, f := range uefiSourceFilesInsecure {
uefiSourcePaths = append(uefiSourcePaths, filepath.Join(p, f))
}
}
return uefiSourcePaths, uefiVarsPaths
}
// QemuExecutable returns name of qemu executable for the arch.
func (arch Arch) QemuExecutable() string {
binaries := []string{
"qemu-system-" + arch.QemuArch(),
"qemu-kvm",
"/usr/libexec/qemu-kvm",
}
for _, binary := range binaries {
if path, err := exec.LookPath(binary); err == nil {
return path
}
}
return ""
}
// TPMDeviceArgs returns arguments for qemu to enable TPM device.
func (arch Arch) TPMDeviceArgs(socketPath string) []string {
tpmDeviceArgs := []string{
"-chardev",
fmt.Sprintf("socket,id=chrtpm,path=%s", socketPath),
"-tpmdev",
"emulator,id=tpm0,chardev=chrtpm",
"-device",
}
switch arch {
case ArchAmd64:
return slices.Concat(tpmDeviceArgs, []string{"tpm-tis,tpmdev=tpm0"})
case ArchArm64:
return slices.Concat(tpmDeviceArgs, []string{"tpm-tis-device,tpmdev=tpm0"})
default:
panic("unsupported architecture")
}
}
func (arch Arch) getMachineArgs(iommu bool) []string {
args := arch.QemuMachine()
if arch.acceleratorAvailable() {
args += ",accel=" + accelerator
}
// ref: https://wiki.qemu.org/Features/VT-d
if iommu {
args += ",kernel-irqchip=split"
}
if arch == ArchAmd64 {
args += ",smm=on"
}
return []string{"-machine", args}
}