Skip to content

Commit 3750dcd

Browse files
committed
Add optional loading of UEFI firmware via -bios parameter in QEMU
Limiting this to X8664 arch as there is no information if this works for other architectures. This allows to load UEFI firmware on Windows hosts, where pflash can't be used in combination with WHPX acceleration. Default to false on non-Windows hosts and true on Windows (to have working default). Signed-off-by: Arthur Sengileyev <[email protected]>
1 parent 95f0262 commit 3750dcd

File tree

2 files changed

+110
-16
lines changed

2 files changed

+110
-16
lines changed

pkg/qemu/qemu.go

+109-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"io"
910
"io/fs"
1011
"os"
1112
"os/exec"
@@ -598,36 +599,89 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
598599
}
599600
if !legacyBIOS {
600601
var firmware string
602+
firmwareInBios := runtime.GOOS == "windows"
603+
if envVar := os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"); envVar != "" {
604+
b, err := strconv.ParseBool(os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"))
605+
if err != nil {
606+
logrus.WithError(err).Warnf("invalid LIMA_QEMU_UEFI_IN_BIOS value %q", envVar)
607+
} else {
608+
firmwareInBios = b
609+
}
610+
}
611+
firmwareInBios = firmwareInBios && *y.Arch == limayaml.X8664
601612
downloadedFirmware := filepath.Join(cfg.InstanceDir, filenames.QemuEfiCodeFD)
602-
if _, stErr := os.Stat(downloadedFirmware); errors.Is(stErr, os.ErrNotExist) {
603-
loop:
604-
for _, f := range y.Firmware.Images {
605-
switch f.VMType {
606-
case "", limayaml.QEMU:
607-
if f.Arch == *y.Arch {
608-
if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil {
609-
logrus.WithError(err).Warnf("failed to download %q", f.Location)
610-
continue loop
613+
firmwareWithVars := filepath.Join(cfg.InstanceDir, filenames.QemuEfiFullFD)
614+
if firmwareInBios {
615+
if _, stErr := os.Stat(firmwareWithVars); stErr == nil {
616+
firmware = firmwareWithVars
617+
logrus.Infof("Using existing firmware (%q)", firmware)
618+
}
619+
} else {
620+
if _, stErr := os.Stat(downloadedFirmware); errors.Is(stErr, os.ErrNotExist) {
621+
loop:
622+
for _, f := range y.Firmware.Images {
623+
switch f.VMType {
624+
case "", limayaml.QEMU:
625+
if f.Arch == *y.Arch {
626+
if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil {
627+
logrus.WithError(err).Warnf("failed to download %q", f.Location)
628+
continue loop
629+
}
630+
firmware = downloadedFirmware
631+
logrus.Infof("Using firmware %q (downloaded from %q)", firmware, f.Location)
632+
break loop
611633
}
612-
firmware = downloadedFirmware
613-
logrus.Infof("Using firmware %q (downloaded from %q)", firmware, f.Location)
614-
break loop
615634
}
616635
}
636+
} else {
637+
firmware = downloadedFirmware
638+
logrus.Infof("Using existing firmware (%q)", firmware)
617639
}
618-
} else {
619-
firmware = downloadedFirmware
620-
logrus.Infof("Using existing firmware (%q)", firmware)
621640
}
622641
if firmware == "" {
623642
firmware, err = getFirmware(exe, *y.Arch)
624643
if err != nil {
625644
return "", nil, err
626645
}
627646
logrus.Infof("Using system firmware (%q)", firmware)
647+
if firmwareInBios {
648+
firmwareVars, err := getFirmwareVars(exe, *y.Arch)
649+
if err != nil {
650+
return "", nil, err
651+
}
652+
logrus.Infof("Using system firmware vars (%q)", firmwareVars)
653+
varsFile, err := os.Open(firmwareVars)
654+
if err != nil {
655+
return "", nil, err
656+
}
657+
defer varsFile.Close()
658+
codeFile, err := os.Open(firmware)
659+
if err != nil {
660+
return "", nil, err
661+
}
662+
defer codeFile.Close()
663+
resultFile, err := os.OpenFile(firmwareWithVars, os.O_CREATE|os.O_WRONLY, 0o644)
664+
if err != nil {
665+
return "", nil, err
666+
}
667+
defer resultFile.Close()
668+
_, err = io.Copy(resultFile, varsFile)
669+
if err != nil {
670+
return "", nil, err
671+
}
672+
_, err = io.Copy(resultFile, codeFile)
673+
if err != nil {
674+
return "", nil, err
675+
}
676+
firmware = firmwareWithVars
677+
}
628678
}
629679
if firmware != "" {
630-
args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware))
680+
if firmwareInBios {
681+
args = append(args, "-bios", firmware)
682+
} else {
683+
args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware))
684+
}
631685
}
632686
}
633687

@@ -1121,9 +1175,11 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
11211175
userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
11221176

11231177
relativePath := fmt.Sprintf("share/qemu/edk2-%s-code.fd", qemuEdk2Arch(arch))
1178+
relativePathWin := fmt.Sprintf("share/edk2-%s-code.fd", qemuEdk2Arch(arch))
11241179
candidates := []string{
11251180
filepath.Join(userLocalDir, relativePath), // XDG-like
11261181
filepath.Join(localDir, relativePath), // macOS (homebrew)
1182+
filepath.Join(binDir, relativePathWin), // Windows installer
11271183
}
11281184

11291185
switch arch {
@@ -1165,3 +1221,40 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
11651221
qemuArch := strings.TrimPrefix(filepath.Base(qemuExe), "qemu-system-")
11661222
return "", fmt.Errorf("could not find firmware for %q (hint: try copying the \"edk-%s-code.fd\" firmware to $HOME/.local/share/qemu/)", arch, qemuArch)
11671223
}
1224+
1225+
func getFirmwareVars(qemuExe string, arch limayaml.Arch) (string, error) {
1226+
var targetArch string
1227+
switch arch {
1228+
case limayaml.X8664:
1229+
targetArch = "i386" // vars are unified between i386 and x86_64 and normally only former is bundled
1230+
default:
1231+
return "", fmt.Errorf("unexpected architecture: %q", arch)
1232+
}
1233+
1234+
currentUser, err := user.Current()
1235+
if err != nil {
1236+
return "", err
1237+
}
1238+
1239+
binDir := filepath.Dir(qemuExe) // "/usr/local/bin"
1240+
localDir := filepath.Dir(binDir) // "/usr/local"
1241+
userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
1242+
1243+
relativePath := fmt.Sprintf("share/qemu/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
1244+
relativePathWin := fmt.Sprintf("share/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
1245+
candidates := []string{
1246+
filepath.Join(userLocalDir, relativePath), // XDG-like
1247+
filepath.Join(localDir, relativePath), // macOS (homebrew)
1248+
filepath.Join(binDir, relativePathWin), // Windows installer
1249+
}
1250+
1251+
logrus.Debugf("firmware vars candidates = %v", candidates)
1252+
1253+
for _, f := range candidates {
1254+
if _, err := os.Stat(f); err == nil {
1255+
return f, nil
1256+
}
1257+
}
1258+
1259+
return "", fmt.Errorf("could not find firmware vars for %q", arch)
1260+
}

pkg/store/filenames/filenames.go

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const (
5757
VzIdentifier = "vz-identifier"
5858
VzEfi = "vz-efi" // efi variable store
5959
QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created
60+
QemuEfiFullFD = "qemu-efi-full.fd" // concatenated efi vars and code; not always created
6061
AnsibleInventoryYAML = "ansible-inventory.yaml"
6162

6263
// SocketDir is the default location for forwarded sockets with a relative paths in HostSocket.

0 commit comments

Comments
 (0)