Skip to content

Commit 4f86066

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 b55086f commit 4f86066

File tree

5 files changed

+101
-3
lines changed

5 files changed

+101
-3
lines changed

Diff for: pkg/limayaml/defaults.go

+10
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,16 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) {
394394
y.Firmware.LegacyBIOS = ptr.Of(false)
395395
}
396396

397+
if y.Firmware.CompatUEFIViaBIOS == nil {
398+
y.Firmware.CompatUEFIViaBIOS = d.Firmware.CompatUEFIViaBIOS
399+
}
400+
if o.Firmware.CompatUEFIViaBIOS != nil {
401+
y.Firmware.CompatUEFIViaBIOS = o.Firmware.CompatUEFIViaBIOS
402+
}
403+
if y.Firmware.CompatUEFIViaBIOS == nil {
404+
y.Firmware.CompatUEFIViaBIOS = ptr.Of(runtime.GOOS == "windows")
405+
}
406+
397407
y.Firmware.Images = append(append(o.Firmware.Images, y.Firmware.Images...), d.Firmware.Images...)
398408
for i := range y.Firmware.Images {
399409
f := &y.Firmware.Images[i]

Diff for: pkg/limayaml/defaults_test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ func TestFillDefault(t *testing.T) {
9292
},
9393
TimeZone: ptr.Of(hostTimeZone()),
9494
Firmware: Firmware{
95-
LegacyBIOS: ptr.Of(false),
95+
LegacyBIOS: ptr.Of(false),
96+
CompatUEFIViaBIOS: ptr.Of(runtime.GOOS == "windows"),
9697
},
9798
Audio: Audio{
9899
Device: ptr.Of(""),
@@ -207,6 +208,7 @@ func TestFillDefault(t *testing.T) {
207208
VMType: QEMU,
208209
},
209210
},
211+
CompatUEFIViaBIOS: ptr.Of(false),
210212
},
211213
}
212214

@@ -361,6 +363,7 @@ func TestFillDefault(t *testing.T) {
361363
},
362364
},
363365
},
366+
CompatUEFIViaBIOS: ptr.Of(runtime.GOOS != "windows"),
364367
},
365368
Audio: Audio{
366369
Device: ptr.Of("coreaudio"),
@@ -568,7 +571,8 @@ func TestFillDefault(t *testing.T) {
568571
},
569572
TimeZone: ptr.Of("Universal"),
570573
Firmware: Firmware{
571-
LegacyBIOS: ptr.Of(true),
574+
LegacyBIOS: ptr.Of(true),
575+
CompatUEFIViaBIOS: ptr.Of(true),
572576
},
573577
Audio: Audio{
574578
Device: ptr.Of("coreaudio"),

Diff for: pkg/limayaml/limayaml.go

+5
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ type Firmware struct {
182182
// LegacyBIOS is ignored for aarch64.
183183
LegacyBIOS *bool `yaml:"legacyBIOS,omitempty" json:"legacyBIOS,omitempty" jsonschema:"nullable"`
184184

185+
// CompatUEFIViaBIOS forces QEMU to load concatenated firmware with -bios option
186+
// CompatUEFIViaBIOS is ignored for non x86_64
187+
// This should be deprecated, if the issue in QEMU is fixed https://gitlab.com/qemu-project/qemu/-/issues/513
188+
CompatUEFIViaBIOS *bool `yaml:"compatUEFIInBIOS" json:"compatUEFIInBIOS,omitempty" jsonschema:"nullable"` // default: false on non Windows and true on Windows
189+
185190
// Images specify UEFI images (edk2-aarch64-code.fd.gz).
186191
// Defaults to built-in UEFI.
187192
Images []FileWithVMType `yaml:"images,omitempty" json:"images,omitempty"`

Diff for: pkg/qemu/qemu.go

+77-1
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"
@@ -624,9 +625,45 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
624625
return "", nil, err
625626
}
626627
logrus.Infof("Using system firmware (%q)", firmware)
628+
if *y.Firmware.CompatUEFIViaBIOS && *y.Arch == limayaml.X8664 {
629+
firmwareVars, err := getFirmwareVars(exe, *y.Arch)
630+
if err != nil {
631+
return "", nil, err
632+
}
633+
logrus.Infof("Using system firmware vars (%q)", firmwareVars)
634+
// TODO
635+
varsFile, err := os.Open(firmwareVars)
636+
if err != nil {
637+
return "", nil, err
638+
}
639+
defer varsFile.Close()
640+
codeFile, err := os.Open(firmware)
641+
if err != nil {
642+
return "", nil, err
643+
}
644+
defer codeFile.Close()
645+
downloadedFile, err := os.OpenFile(downloadedFirmware, os.O_CREATE|os.O_WRONLY, 0644)
646+
if err != nil {
647+
return "", nil, err
648+
}
649+
defer downloadedFile.Close()
650+
_, err = io.Copy(downloadedFile, varsFile)
651+
if err != nil {
652+
return "", nil, err
653+
}
654+
_, err = io.Copy(downloadedFile, codeFile)
655+
if err != nil {
656+
return "", nil, err
657+
}
658+
firmware = downloadedFirmware
659+
}
627660
}
628661
if firmware != "" {
629-
args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware))
662+
if *y.Firmware.CompatUEFIViaBIOS && *y.Arch == limayaml.X8664 {
663+
args = append(args, "-bios", firmware)
664+
} else {
665+
args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware))
666+
}
630667
}
631668
}
632669

@@ -1120,9 +1157,11 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
11201157
userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
11211158

11221159
relativePath := fmt.Sprintf("share/qemu/edk2-%s-code.fd", qemuEdk2Arch(arch))
1160+
relativePathWin := fmt.Sprintf("share/edk2-%s-code.fd", qemuEdk2Arch(arch))
11231161
candidates := []string{
11241162
filepath.Join(userLocalDir, relativePath), // XDG-like
11251163
filepath.Join(localDir, relativePath), // macOS (homebrew)
1164+
filepath.Join(binDir, relativePathWin), // Windows installer
11261165
}
11271166

11281167
switch arch {
@@ -1164,3 +1203,40 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
11641203
qemuArch := strings.TrimPrefix(filepath.Base(qemuExe), "qemu-system-")
11651204
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)
11661205
}
1206+
1207+
func getFirmwareVars(qemuExe string, arch limayaml.Arch) (string, error) {
1208+
targetArch := arch
1209+
switch arch {
1210+
case limayaml.X8664:
1211+
targetArch = "i386"
1212+
default:
1213+
return "", fmt.Errorf("unexpected architecture: %q", arch)
1214+
}
1215+
1216+
currentUser, err := user.Current()
1217+
if err != nil {
1218+
return "", err
1219+
}
1220+
1221+
binDir := filepath.Dir(qemuExe) // "/usr/local/bin"
1222+
localDir := filepath.Dir(binDir) // "/usr/local"
1223+
userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
1224+
1225+
relativePath := fmt.Sprintf("share/qemu/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
1226+
relativePathWin := fmt.Sprintf("share/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
1227+
candidates := []string{
1228+
filepath.Join(userLocalDir, relativePath), // XDG-like
1229+
filepath.Join(localDir, relativePath), // macOS (homebrew)
1230+
filepath.Join(binDir, relativePathWin), // Windows installer
1231+
}
1232+
1233+
logrus.Debugf("firmware vars candidates = %v", candidates)
1234+
1235+
for _, f := range candidates {
1236+
if _, err := os.Stat(f); err == nil {
1237+
return f, nil
1238+
}
1239+
}
1240+
1241+
return "", fmt.Errorf("could not find firmware vars for %q", arch)
1242+
}

Diff for: templates/default.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ firmware:
345345
# arch: "aarch64"
346346
# digest: "sha256:..."
347347
# vmType: "qemu"
348+
# Loads UEFI usigin -bios parameter for compatibility reasons. Applied only for QEMU x86_64.
349+
# 🟢 Builtin default: false on non-Windows and true on Windows
350+
compatUEFIInBIOS: null
348351

349352
audio:
350353
# EXPERIMENTAL

0 commit comments

Comments
 (0)