Skip to content

Commit 26a3821

Browse files
authored
Merge pull request #3261 from arixmkii/bios-uefi
Add optional loading of UEFI firmware via -bios parameter in QEMU
2 parents 3b14bee + e3fd241 commit 26a3821

File tree

3 files changed

+121
-16
lines changed

3 files changed

+121
-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.

website/content/en/docs/config/environment-variables.md

+11
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,14 @@ This page documents the environment variables used in Lima.
6666
```sh
6767
export LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT=5
6868
```
69+
70+
### `LIMA_QEMU_UEFI_IN_BIOS`
71+
72+
- **Description**: Commands QEMU to load x86_64 UEFI images using `-bios` instead of `pflash` drives.
73+
- **Default**: `false` on Unix like hosts and `true` on Windows hosts
74+
- **Usage**:
75+
```sh
76+
export LIMA_QEMU_UEFI_IN_BIOS=true
77+
```
78+
- **Note**: It is expected that this variable will be set to `false` by default in future
79+
when QEMU supports `pflash` UEFI for accelerated guests on Windows.

0 commit comments

Comments
 (0)