Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional loading of UEFI firmware via -bios parameter in QEMU #3261

Merged
merged 1 commit into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 109 additions & 16 deletions pkg/qemu/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
Expand Down Expand Up @@ -598,36 +599,89 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
}
if !legacyBIOS {
var firmware string
firmwareInBios := runtime.GOOS == "windows"
if envVar := os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"); envVar != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

b, err := strconv.ParseBool(os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"))
if err != nil {
logrus.WithError(err).Warnf("invalid LIMA_QEMU_UEFI_IN_BIOS value %q", envVar)
} else {
firmwareInBios = b
}
}
firmwareInBios = firmwareInBios && *y.Arch == limayaml.X8664
downloadedFirmware := filepath.Join(cfg.InstanceDir, filenames.QemuEfiCodeFD)
if _, stErr := os.Stat(downloadedFirmware); errors.Is(stErr, os.ErrNotExist) {
loop:
for _, f := range y.Firmware.Images {
switch f.VMType {
case "", limayaml.QEMU:
if f.Arch == *y.Arch {
if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil {
logrus.WithError(err).Warnf("failed to download %q", f.Location)
continue loop
firmwareWithVars := filepath.Join(cfg.InstanceDir, filenames.QemuEfiFullFD)
if firmwareInBios {
if _, stErr := os.Stat(firmwareWithVars); stErr == nil {
firmware = firmwareWithVars
logrus.Infof("Using existing firmware (%q)", firmware)
}
} else {
if _, stErr := os.Stat(downloadedFirmware); errors.Is(stErr, os.ErrNotExist) {
loop:
for _, f := range y.Firmware.Images {
switch f.VMType {
case "", limayaml.QEMU:
if f.Arch == *y.Arch {
if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil {
logrus.WithError(err).Warnf("failed to download %q", f.Location)
continue loop
}
firmware = downloadedFirmware
logrus.Infof("Using firmware %q (downloaded from %q)", firmware, f.Location)
break loop
}
firmware = downloadedFirmware
logrus.Infof("Using firmware %q (downloaded from %q)", firmware, f.Location)
break loop
}
}
} else {
firmware = downloadedFirmware
logrus.Infof("Using existing firmware (%q)", firmware)
}
} else {
firmware = downloadedFirmware
logrus.Infof("Using existing firmware (%q)", firmware)
}
if firmware == "" {
firmware, err = getFirmware(exe, *y.Arch)
if err != nil {
return "", nil, err
}
logrus.Infof("Using system firmware (%q)", firmware)
if firmwareInBios {
firmwareVars, err := getFirmwareVars(exe, *y.Arch)
if err != nil {
return "", nil, err
}
logrus.Infof("Using system firmware vars (%q)", firmwareVars)
varsFile, err := os.Open(firmwareVars)
if err != nil {
return "", nil, err
}
defer varsFile.Close()
codeFile, err := os.Open(firmware)
if err != nil {
return "", nil, err
}
defer codeFile.Close()
resultFile, err := os.OpenFile(firmwareWithVars, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return "", nil, err
}
defer resultFile.Close()
_, err = io.Copy(resultFile, varsFile)
if err != nil {
return "", nil, err
}
_, err = io.Copy(resultFile, codeFile)
if err != nil {
return "", nil, err
}
firmware = firmwareWithVars
}
}
if firmware != "" {
args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware))
if firmwareInBios {
args = append(args, "-bios", firmware)
} else {
args = append(args, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", firmware))
}
}
}

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

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

switch arch {
Expand Down Expand Up @@ -1165,3 +1221,40 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
qemuArch := strings.TrimPrefix(filepath.Base(qemuExe), "qemu-system-")
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)
}

func getFirmwareVars(qemuExe string, arch limayaml.Arch) (string, error) {
var targetArch string
switch arch {
case limayaml.X8664:
targetArch = "i386" // vars are unified between i386 and x86_64 and normally only former is bundled
default:
return "", fmt.Errorf("unexpected architecture: %q", arch)
}

currentUser, err := user.Current()
if err != nil {
return "", err
}

binDir := filepath.Dir(qemuExe) // "/usr/local/bin"
localDir := filepath.Dir(binDir) // "/usr/local"
userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"

relativePath := fmt.Sprintf("share/qemu/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
relativePathWin := fmt.Sprintf("share/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
candidates := []string{
filepath.Join(userLocalDir, relativePath), // XDG-like
filepath.Join(localDir, relativePath), // macOS (homebrew)
filepath.Join(binDir, relativePathWin), // Windows installer
}

logrus.Debugf("firmware vars candidates = %v", candidates)

for _, f := range candidates {
if _, err := os.Stat(f); err == nil {
return f, nil
}
}

return "", fmt.Errorf("could not find firmware vars for %q", arch)
}
1 change: 1 addition & 0 deletions pkg/store/filenames/filenames.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
VzIdentifier = "vz-identifier"
VzEfi = "vz-efi" // efi variable store
QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created
QemuEfiFullFD = "qemu-efi-full.fd" // concatenated efi vars and code; not always created
AnsibleInventoryYAML = "ansible-inventory.yaml"

// SocketDir is the default location for forwarded sockets with a relative paths in HostSocket.
Expand Down
11 changes: 11 additions & 0 deletions website/content/en/docs/config/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,14 @@ This page documents the environment variables used in Lima.
```sh
export LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT=5
```

### `LIMA_QEMU_UEFI_IN_BIOS`

- **Description**: Commands QEMU to load x86_64 UEFI images using `-bios` instead of `pflash` drives.
- **Default**: `false` on Unix like hosts and `true` on Windows hosts
- **Usage**:
```sh
export LIMA_QEMU_UEFI_IN_BIOS=true
```
- **Note**: It is expected that this variable will be set to `false` by default in future
when QEMU supports `pflash` UEFI for accelerated guests on Windows.
Loading