Skip to content

Commit

Permalink
Merge pull request #880 from dustins/aavmf
Browse files Browse the repository at this point in the history
Add support for ARMv8 to use AAVMF EFI firmware
  • Loading branch information
stgraber authored Jun 27, 2024
2 parents 48bf032 + 73afd84 commit 5c0178e
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 95 deletions.
2 changes: 2 additions & 0 deletions doc/.wordlist.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
AAAA
AAVMF
ABI
ACL
ACLs
Expand Down Expand Up @@ -68,6 +69,7 @@ Ebit
eBPF
ECDHE
ECDSA
EDK
EiB
Eibit
endian
Expand Down
2 changes: 1 addition & 1 deletion doc/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Name | Description
`INCUS_EXEC_PATH` | Full path to the Incus binary (used when forking subcommands)
`INCUS_IDMAPPED_MOUNTS_DISABLE` | Disable idmapped mounts support (useful when testing traditional UID shifting)
`INCUS_LXC_TEMPLATE_CONFIG` | Path to the LXC template configuration directory
`INCUS_OVMF_PATH` | Path to an OVMF build including `OVMF_CODE.fd` and `OVMF_VARS.ms.fd`
`INCUS_EDK2_PATH` | Path to EDK2 firmware build including `*_CODE.fd` and `*_VARS.fd`
`INCUS_SECURITY_APPARMOR` | If set to `false`, forces AppArmor off
`INCUS_UI` | Path to the web UI to serve through the web server
`INCUS_USBIDS_PATH` | Path to the hwdata `usb.ids` file
12 changes: 5 additions & 7 deletions doc/installing.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,16 @@ In addition, for normal operation, you'll also likely need
sudo zypper install dnsmasq squashfs xz rsync tar attr acl qemu qemu-img qemu-spice qemu-hw-display-virtio-gpu-pci iptables ebtables nftables
As OpenSUSE stores QEMU firmware files using an unusual filename and location, you will need to create some symlinks for them:
sudo mkdir /usr/share/OVMF
sudo ln -s /usr/share/qemu/ovmf-x86_64-4m-code.bin /usr/share/OVMF/OVMF_CODE.4MB.fd
sudo ln -s /usr/share/qemu/ovmf-x86_64-4m-vars.bin /usr/share/OVMF/OVMF_VARS.4MB.fd
sudo ln -s /usr/share/qemu/ovmf-x86_64-ms-4m-vars.bin /usr/share/OVMF/OVMF_VARS.4MB.ms.fd
sudo ln -s /usr/share/qemu/ovmf-x86_64-ms-4m-code.bin /usr/share/OVMF/OVMF_CODE.4MB.ms.fd
```
````

```{note}
On ARM64 CPUs you need to install AAVMF instead of OVMF for UEFI to work with virtual machines.
In some distributions this is done through a separate package.
```

### From source: Build the latest version

These instructions for building from source are suitable for individual developers who want to build the latest version
Expand Down
35 changes: 27 additions & 8 deletions internal/server/apparmor/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (
"strings"

"github.com/lxc/incus/v6/internal/server/cgroup"
"github.com/lxc/incus/v6/internal/server/instance/drivers/edk2"
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
"github.com/lxc/incus/v6/internal/server/project"
"github.com/lxc/incus/v6/internal/server/sys"
localUtil "github.com/lxc/incus/v6/internal/server/util"
internalUtil "github.com/lxc/incus/v6/internal/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/osarch"
"github.com/lxc/incus/v6/shared/util"
)

Expand Down Expand Up @@ -197,14 +199,31 @@ func instanceProfile(sysOS *sys.OS, inst instance, extraBinaries []string) (stri
return "", err
}

ovmfPath := "/usr/share/OVMF"
if os.Getenv("INCUS_OVMF_PATH") != "" {
ovmfPath = os.Getenv("INCUS_OVMF_PATH")
}
var edk2Paths []string

ovmfPath, err = filepath.EvalSymlinks(ovmfPath)
if err != nil {
return "", err
edk2Path := edk2.GetenvEdk2Path()
if edk2Path != "" {
edk2Path, err := filepath.EvalSymlinks(edk2Path)
if err != nil {
return "", err
}

edk2Paths = append(edk2Paths, edk2Path)
} else {
arch, err := osarch.ArchitectureGetLocalID()

if err == nil {
for _, installation := range edk2.GetArchitectureInstallations(arch) {
if util.PathExists(installation.Path) {
edk2Path, err := filepath.EvalSymlinks(installation.Path)
if err != nil {
return "", err
}

edk2Paths = append(edk2Paths, edk2Path)
}
}
}
}

agentPath := ""
Expand All @@ -231,7 +250,7 @@ func instanceProfile(sysOS *sys.OS, inst instance, extraBinaries []string) (stri
"name": InstanceProfileName(inst),
"path": path,
"raw": rawContent,
"ovmfPath": ovmfPath,
"edk2Paths": edk2Paths,
"agentPath": agentPath,
})
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion internal/server/apparmor/instance_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
/sys/devices/** r,
/sys/module/vhost/** r,
/tmp/incus_sev_* r,
{{ .ovmfPath }}/** kr,
{{- range $index, $element := .edk2Paths }}
{{ $element }}/** kr,
{{- end }}
/usr/share/qemu/** kr,
/usr/share/seabios/** kr,
owner @{PROC}/@{pid}/cpuset r,
Expand Down
114 changes: 36 additions & 78 deletions internal/server/instance/drivers/driver_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
"github.com/lxc/incus/v6/internal/server/device/nictype"
"github.com/lxc/incus/v6/internal/server/instance"
"github.com/lxc/incus/v6/internal/server/instance/drivers/edk2"
"github.com/lxc/incus/v6/internal/server/instance/drivers/qmp"
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
"github.com/lxc/incus/v6/internal/server/instance/operationlock"
Expand Down Expand Up @@ -114,42 +115,6 @@ const qemuBlockDevIDPrefix = "incus_"
// qemuMigrationNBDExportName is the name of the disk device export by the migration NBD server.
const qemuMigrationNBDExportName = "incus_root"

// OVMF firmwares.
type ovmfFirmware struct {
code string
vars string
}

var ovmfGenericFirmwares = []ovmfFirmware{
{code: "OVMF_CODE.4MB.fd", vars: "OVMF_VARS.4MB.fd"},
{code: "OVMF_CODE_4M.fd", vars: "OVMF_VARS_4M.fd"},
{code: "OVMF_CODE.4m.fd", vars: "OVMF_VARS.4m.fd"},
{code: "OVMF_CODE.2MB.fd", vars: "OVMF_VARS.2MB.fd"},
{code: "OVMF_CODE.fd", vars: "OVMF_VARS.fd"},
{code: "OVMF_CODE.fd", vars: "qemu.nvram"},
}

var ovmfSecurebootFirmwares = []ovmfFirmware{
{code: "OVMF_CODE.4MB.fd", vars: "OVMF_VARS.4MB.ms.fd"},
{code: "OVMF_CODE_4M.ms.fd", vars: "OVMF_VARS_4M.ms.fd"},
{code: "OVMF_CODE_4M.secboot.fd", vars: "OVMF_VARS_4M.secboot.fd"},
{code: "OVMF_CODE.secboot.4m.fd", vars: "OVMF_VARS.4m.fd"},
{code: "OVMF_CODE.secboot.fd", vars: "OVMF_VARS.secboot.fd"},
{code: "OVMF_CODE.secboot.fd", vars: "OVMF_VARS.fd"},
{code: "OVMF_CODE.2MB.fd", vars: "OVMF_VARS.2MB.ms.fd"},
{code: "OVMF_CODE.fd", vars: "OVMF_VARS.ms.fd"},
{code: "OVMF_CODE.fd", vars: "qemu.nvram"},
}

var ovmfCSMFirmwares = []ovmfFirmware{
{code: "seabios.bin", vars: "seabios.bin"},
{code: "OVMF_CODE.4MB.CSM.fd", vars: "OVMF_VARS.4MB.CSM.fd"},
{code: "OVMF_CODE.csm.4m.fd", vars: "OVMF_VARS.4m.fd"},
{code: "OVMF_CODE.2MB.CSM.fd", vars: "OVMF_VARS.2MB.CSM.fd"},
{code: "OVMF_CODE.CSM.fd", vars: "OVMF_VARS.CSM.fd"},
{code: "OVMF_CODE.csm.fd", vars: "OVMF_VARS.fd"},
}

// qemuSparseUSBPorts is the amount of sparse USB ports for VMs.
// 4 are reserved, and the other 4 can be used for any USB device.
const qemuSparseUSBPorts = 8
Expand Down Expand Up @@ -795,14 +760,6 @@ func (d *qemu) Rebuild(img *api.Image, op *operations.Operation) error {
return d.rebuildCommon(d, img, op)
}

func (d *qemu) ovmfPath() string {
if os.Getenv("INCUS_OVMF_PATH") != "" {
return os.Getenv("INCUS_OVMF_PATH")
}

return "/usr/share/OVMF"
}

// killQemuProcess kills specified process. Optimistically attempts to wait for the process to fully exit, but does
// not return an error if the Wait call fails. This is because this function is used in scenarios where the daemon has
// been restarted after the VM has been started and is no longer the parent of the QEMU process.
Expand Down Expand Up @@ -1291,7 +1248,7 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error {
return err
}

// Copy OVMF settings firmware to nvram file if needed.
// Copy EDK2 settings firmware to nvram file if needed.
// This firmware file can be modified by the VM so it must be copied from the defaults.
if d.architectureSupportsUEFI(d.architecture) && (!util.PathExists(d.nvramPath()) || util.IsTrue(d.localConfig["volatile.apply_nvram"])) {
err = d.setupNvram()
Expand Down Expand Up @@ -1976,55 +1933,54 @@ func (d *qemu) setupNvram() error {
d.logger.Debug("Generating NVRAM")

// Cleanup existing variables.
for _, firmwares := range [][]ovmfFirmware{ovmfGenericFirmwares, ovmfSecurebootFirmwares, ovmfCSMFirmwares} {
for _, firmware := range firmwares {
err := os.Remove(filepath.Join(d.Path(), firmware.vars))
if err != nil && !os.IsNotExist(err) {
return err
}
for _, firmwarePair := range edk2.GetAchitectureFirmwarePairs(d.architecture) {
err := os.Remove(filepath.Join(d.Path(), filepath.Base(firmwarePair.Vars)))
if err != nil && !os.IsNotExist(err) {
return err
}
}

// Determine expected firmware.
firmwares := ovmfGenericFirmwares
var firmwares []edk2.FirmwarePair
if util.IsTrue(d.expandedConfig["security.csm"]) {
firmwares = ovmfCSMFirmwares
firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.CSM)
} else if util.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]) {
firmwares = ovmfSecurebootFirmwares
firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.SECUREBOOT)
} else {
firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.GENERIC)
}

// Find the template file.
var ovmfVarsPath string
var ovmfVarsName string
var efiVarsPath string
var efiVarsName string
for _, firmware := range firmwares {
varsPath := filepath.Join(d.ovmfPath(), firmware.vars)
varsPath, err = filepath.EvalSymlinks(varsPath)
varsPath, err := filepath.EvalSymlinks(firmware.Vars)
if err != nil {
continue
}

if util.PathExists(varsPath) {
ovmfVarsPath = varsPath
ovmfVarsName = firmware.vars
efiVarsPath = varsPath
efiVarsName = filepath.Base(firmware.Vars)
break
}
}

if ovmfVarsPath == "" {
if efiVarsPath == "" {
return fmt.Errorf("Couldn't find one of the required UEFI firmware files: %+v", firmwares)
}

// Copy the template.
err = internalUtil.FileCopy(ovmfVarsPath, filepath.Join(d.Path(), ovmfVarsName))
err = internalUtil.FileCopy(efiVarsPath, filepath.Join(d.Path(), efiVarsName))
if err != nil {
return err
}

// Generate a symlink if needed.
// This is so qemu.nvram can always be assumed to be the OVMF vars file.
// This is so qemu.nvram can always be assumed to be the EDK2 vars file.
// The real file name is then used to determine what firmware must be selected.
if !util.PathExists(d.nvramPath()) {
err = os.Symlink(ovmfVarsName, d.nvramPath())
err = os.Symlink(efiVarsName, d.nvramPath())
if err != nil {
return err
}
Expand Down Expand Up @@ -3047,27 +3003,29 @@ func (d *qemu) generateQemuConfigFile(cpuInfo *cpuTopology, mountInfo *storagePo
}

// Determine expected firmware.
firmwares := ovmfGenericFirmwares
var firmwares []edk2.FirmwarePair
if util.IsTrue(d.expandedConfig["security.csm"]) {
firmwares = ovmfCSMFirmwares
firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.CSM)
} else if util.IsTrueOrEmpty(d.expandedConfig["security.secureboot"]) {
firmwares = ovmfSecurebootFirmwares
firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.SECUREBOOT)
} else {
firmwares = edk2.GetArchitectureFirmwarePairsForUsage(d.architecture, edk2.GENERIC)
}

var ovmfCode string
var efiCode string
for _, firmware := range firmwares {
if util.PathExists(filepath.Join(d.Path(), firmware.vars)) {
ovmfCode = firmware.code
if util.PathExists(filepath.Join(d.Path(), filepath.Base(firmware.Vars))) {
efiCode = firmware.Code
break
}
}

if ovmfCode == "" {
if efiCode == "" {
return "", nil, fmt.Errorf("Unable to locate matching firmware: %+v", firmwares)
}

driveFirmwareOpts := qemuDriveFirmwareOpts{
roPath: filepath.Join(d.ovmfPath(), ovmfCode),
roPath: efiCode,
nvramPath: fmt.Sprintf("/dev/fd/%d", d.addFileDescriptor(fdFiles, nvRAMFile)),
}

Expand Down Expand Up @@ -8473,19 +8431,19 @@ func (d *qemu) checkFeatures(hostArch int, qemuPath string) (map[string]any, err

if d.architectureSupportsUEFI(hostArch) {
// Try to locate a UEFI firmware.
var ovmfPath string
for _, entry := range ovmfGenericFirmwares {
if util.PathExists(filepath.Join(d.ovmfPath(), entry.code)) {
ovmfPath = filepath.Join(d.ovmfPath(), entry.code)
var efiPath string
for _, firmwarePair := range edk2.GetArchitectureFirmwarePairsForUsage(hostArch, edk2.GENERIC) {
if util.PathExists(firmwarePair.Code) {
efiPath = firmwarePair.Code
break
}
}

if ovmfPath == "" {
if efiPath == "" {
return nil, fmt.Errorf("Unable to locate a UEFI firmware")
}

qemuArgs = append(qemuArgs, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", ovmfPath))
qemuArgs = append(qemuArgs, "-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", efiPath))
}

var stderr bytes.Buffer
Expand Down
Loading

0 comments on commit 5c0178e

Please sign in to comment.